feat(web-admin): add image preview transform helpers for fit/zoom.
Pure functions for AttachmentUploadField image preview rotate/zoom math, with a Node assert script. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8435836d81
commit
0af792f535
2 files changed
+99
No files matched your search
@@ -0,0 +1,34 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {
|
||||
IMAGE_PREVIEW_ZOOM_MIN,
|
||||
IMAGE_PREVIEW_ZOOM_MAX,
|
||||
IMAGE_PREVIEW_ZOOM_STEP,
|
||||
clampPreviewZoom,
|
||||
computeFitBaseScale,
|
||||
buildPreviewTransformCss
|
||||
} from '../src/utils/image-preview-transform.js'
|
||||
|
||||
assert.equal(IMAGE_PREVIEW_ZOOM_MIN, 0.5)
|
||||
assert.equal(IMAGE_PREVIEW_ZOOM_MAX, 5)
|
||||
assert.equal(IMAGE_PREVIEW_ZOOM_STEP, 0.15)
|
||||
|
||||
assert.equal(clampPreviewZoom(0.1), 0.5)
|
||||
assert.equal(clampPreviewZoom(9), 5)
|
||||
assert.equal(clampPreviewZoom(1.234), 1.23)
|
||||
|
||||
// 未旋转:640x400 装入 320x200 → scale = 0.5
|
||||
assert.ok(
|
||||
Math.abs(computeFitBaseScale(640, 400, 0, 320, 200) - 0.5) < 1e-9
|
||||
)
|
||||
|
||||
// 旋转 90°:外接 400x640 装入 320x200 → scale = min(320/400, 200/640) = 0.3125
|
||||
assert.ok(
|
||||
Math.abs(computeFitBaseScale(640, 400, 90, 320, 200) - 0.3125) < 1e-9
|
||||
)
|
||||
|
||||
assert.equal(
|
||||
buildPreviewTransformCss(10, -20, 90, 0.5),
|
||||
'translate(10px, -20px) rotate(90deg) scale(0.5)'
|
||||
)
|
||||
|
||||
console.log('image-preview-transform ok')
|
||||
@@ -0,0 +1,65 @@
|
||||
/** @typedef {{ x: number, y: number }} PreviewOffset */
|
||||
|
||||
export const IMAGE_PREVIEW_ZOOM_MIN = 0.5
|
||||
export const IMAGE_PREVIEW_ZOOM_MAX = 5
|
||||
export const IMAGE_PREVIEW_ZOOM_STEP = 0.15
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
* @returns {number}
|
||||
*/
|
||||
export function clampPreviewZoom(value) {
|
||||
const n = Number(value)
|
||||
if (!Number.isFinite(n)) return 1
|
||||
const clamped = Math.min(IMAGE_PREVIEW_ZOOM_MAX, Math.max(IMAGE_PREVIEW_ZOOM_MIN, n))
|
||||
return Math.round(clamped * 100) / 100
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转后外接矩形完整装入 stage 的基础缩放(zoom=1 时使用)。
|
||||
* @param {number} naturalWidth
|
||||
* @param {number} naturalHeight
|
||||
* @param {number} angleDeg
|
||||
* @param {number} stageWidth
|
||||
* @param {number} stageHeight
|
||||
* @returns {number}
|
||||
*/
|
||||
export function computeFitBaseScale(
|
||||
naturalWidth,
|
||||
naturalHeight,
|
||||
angleDeg,
|
||||
stageWidth,
|
||||
stageHeight
|
||||
) {
|
||||
const natW = Math.max(Number(naturalWidth) || 1, 1)
|
||||
const natH = Math.max(Number(naturalHeight) || 1, 1)
|
||||
const stageW = Math.max(Number(stageWidth) || 1, 1)
|
||||
const stageH = Math.max(Number(stageHeight) || 1, 1)
|
||||
const rad = ((Number(angleDeg) || 0) * Math.PI) / 180
|
||||
const cos = Math.abs(Math.cos(rad))
|
||||
const sin = Math.abs(Math.sin(rad))
|
||||
const boundW = natW * cos + natH * sin
|
||||
const boundH = natW * sin + natH * cos
|
||||
return Math.min(stageW / boundW, stageH / boundH)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} offsetX
|
||||
* @param {number} offsetY
|
||||
* @param {number} angleDeg
|
||||
* @param {number} scale
|
||||
* @returns {string}
|
||||
*/
|
||||
export function buildPreviewTransformCss(offsetX, offsetY, angleDeg, scale) {
|
||||
return (
|
||||
'translate(' +
|
||||
offsetX +
|
||||
'px, ' +
|
||||
offsetY +
|
||||
'px) rotate(' +
|
||||
angleDeg +
|
||||
'deg) scale(' +
|
||||
scale +
|
||||
')'
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user