[5.x]: Image editor crop can't be resized when the crop box starts a fraction of a pixel outside the image
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 62/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- javascript
- 領域
- frontend
調査の方向性
The bug lives in the image editor's crop code, in _resizeCropperByHandleAndDeltas() and arePointsInsideRectangle(), which the issue names. Start by reproducing with the 1414×994 image in a 1440×900 window, then log the crop box and imageVerticeCoords on opening the Crop tab to see the sub-pixel overhang. Done means a corner drag with Unconstrained selected resizes the box in that setup, and dragging well past the image is still rejected. The fix should also cover the alternative of clamping in enableCropMode(), which the issue suggests.
索引モデルが issue の本文から書いたものです。
説明
What happened?
Description
n the image editor's Crop tab, the crop handles sometimes can't be dragged at all, even with the "Unconstrained" constraint selected. The handles are picked up (the resize cursor shows and handlePicked is set), but every resize is rejected and the crop box doesn't change. Selecting another constraint and then "Unconstrained" again sometimes fixes it, but not always – it depends on which constraint was picked.
It depends on the image dimensions and the browser window size, so it looks random. Reproduced in Chrome and Brave, with identical results in both.
Cause
_resizeCropperByHandleAndDeltas() rejects a resize unless all four corners of the new crop rectangle pass arePointsInsideRectangle(…, this.imageVerticeCoords). That check has no tolerance, but when crop mode opens, the cropper is restored from cropperState (in enableCropMode()) while imageVerticeCoords comes from getImageVerticeCoords('fit'). The two are calculated differently and don't always agree, so the crop box can start a fraction of a pixel outside the image. When it does, the corners that aren't being dragged are still outside, so every resize fails.
Example – a 1414×994 image in a 1440×900 browser window, values read from the editor instance on opening the Crop tab:
| Top | Bottom | |
|---|---|---|
Image (imageVerticeCoords) |
11.5 | 816.5 |
Crop box (clipper) |
11.366 | 816.634 |
The crop box overhangs the image by ~0.13px at the top and bottom, and 16 out of 16 resize attempts (4 drags on each corner handle) were rejected.
The same image at other window sizes works, because the rounding happens to land inside:
| Window | Crop box top / bottom vs image | Resizes accepted |
|---|---|---|
| 1440×900 | 0.13px outside | 0 / 16 |
| 1280×800 | 0.02px inside | 16 / 16 |
| 1512×945 | 0.05px inside | 16 / 16 |
The workaround of selecting another constraint and then "Unconstrained" only works when enforceCroppingConstraint() leaves the box inside the image. With the same image and window:
- Original → Unconstrained: the box becomes 1145×805 inside a 1145.66×805 image → works (16/16)
- 16:9 → Unconstrained: the box becomes 1146px wide, wider than the 1145.66px image (left edge at -0.17) → still broken (0/16)
Steps to reproduce
- Upload an image 1414×994px in size.
- Set the browser viewport to 1440×900.
- Open the asset's edit page and click "Edit Image".
- Go to the Crop tab. "Unconstrained" is selected.
- Drag any corner handle inwards.
(Other image and window size combinations trigger it too – any where the restored crop box lands slightly outside the image.)
Expected behavior
The crop box can be resized freely with "Unconstrained" selected.
Actual behavior
The handle is grabbed but the crop box doesn't resize. Selecting another constraint and then "Unconstrained" sometimes makes it work, depending on the constraint.
Possible fix
Allow a little tolerance in arePointsInsideRectangle() for floating point error. Patching it in the browser to allow points up to 1px outside the rectangle fixed both the initial case and the 16:9 → Unconstrained case (16/16 resizes accepted in each), while dragging the crop box well past the image is still rejected:
arePointsInsideRectangle: function (points, rectangle) {
const ab = this._getVector(rectangle.a, rectangle.b);
const bc = this._getVector(rectangle.b, rectangle.c);
const scalarAbAb = this._getScalarProduct(ab, ab);
const scalarBcBc = this._getScalarProduct(bc, bc);
// Allow up to 1px outside the rectangle, for floating point error
const toleranceAb = Math.sqrt(scalarAbAb);
const toleranceBc = Math.sqrt(scalarBcBc);
return points.every((point) => {
const scalarAbAp = this._getScalarProduct(ab, this._getVector(rectangle.a, point));
const scalarBcBp = this._getScalarProduct(bc, this._getVector(rectangle.b, point));
return (
scalarAbAp >= -toleranceAb &&
scalarAbAp <= scalarAbAb + toleranceAb &&
scalarBcBp >= -toleranceBc &&
scalarBcBp <= scalarBcBc + toleranceBc
);
});
},
An alternative would be to clamp the crop box to the image bounds when it's restored in enableCropMode() and after enforceCroppingConstraint() finishes.
This isn't a recent regression – the same strict check is in _handleCropperResize() on 4.x.
Related: the handle grab areas are very small
Separate from the above, but it makes the problem harder to tell apart: the areas that pick up a handle in _cropperHandleHitTest() are only a few pixels, and part of each is outside the crop box. When the crop box sits on the edge of the canvas – which it does on open, on whichever sides the image fills the editor – that outside part is off the canvas and can't be clicked.
Measured by moving the mouse 1px at a time across each handle, with a 1104×828 (fitted) landscape image in a 1440×900 window. The image fills the canvas height, with black space to the left and right. Negative is outside the crop box:
| Handle | Pixels that pick it up | Width |
|---|---|---|
| Top | 0 to 3 | 4px – nothing outside, as the box is on the canvas edge |
| Bottom | 1 to 3 | 3px – same |
| Left | -2 to 3 | 6px |
| Right | -1 to 4 | 6px |
| Bottom-right corner | 1 to 8 | 8px |
So on open, the top and bottom edges of a landscape image (or the left and right of a portrait one) can only be picked up within a 3–4px strip, which is easy to miss – especially with the page zoomed out. Larger hit areas (e.g. ±8px), kept inside the canvas when the box is on its edge, would help.
Craft CMS version: 5.11.5.1
Craft CMS version
5.11.5.1
PHP version
8.5.7
Operating system and version
MacOS for browsers
Database type and version
MySQL 8.4.5
Image driver and version
ImageMagick 7.1.1-43
Installed plugins and versions
- Asset Usage 4.3.0
- CKEditor 5.9.0
- Contact Form 3.1.0
- Contact Form Extensions 5.0.0
- Contact Form Honeypot 2.1.0
- Craft MCP 1.4.0
- Field Manager 4.0.9
- oEmbed 3.2.2
- SEO 5.0.0
- Vite 5.0.2
- 主要言語
- PHP
- スター
- 3.6k
- フォーク
- 708
- 平均マージ
- 10時間 57分
- マージ済み PR(30日)
- 275
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
craftcms/cms のほかの issue
-
repo:cms
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
craftcms/cms#20052 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug repo:cms
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
craftcms/cms#20051 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[6.x]: Colored chips: link blue has low contrast on tinted backgrounds対応中かも @brandonkelly が 5 日前に担当しました。 オープンbug repo:cms
craftcms/cms#19984 · コメント 3 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
[6.x]: Text in some input fields is clipped at the bottom on Windows対応中かも @brianjhanson が 11 日前に担当しました。 オープンbug repo:cms
craftcms/cms#19928 · コメント 4 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
bug repo:cms
難易度 4/5 3〜5日 初心者へのやさしさ 15/100
craftcms/cms#19916 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
似ている issue
-
sync-en
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
[Sync EN] Document Error thrown by shm_* functions on a detached segment (#5975)対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンsync-en
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 3 日以内に返信
-
Перевод устарел
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
-
新版升级一下对应的依赖可以么オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100