Fix admin cover crop zoom controls.
Remove full-bleed mask inset that blocked visible zoom, and zoom around the crop center. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
-18
@@ -55,7 +55,7 @@
|
|||||||
max-width: none; cursor: grab;
|
max-width: none; cursor: grab;
|
||||||
}
|
}
|
||||||
.crop-mask {
|
.crop-mask {
|
||||||
position: absolute; inset: 0; pointer-events: none;
|
position: absolute; left: 0; top: 0; pointer-events: none;
|
||||||
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45);
|
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45);
|
||||||
border: 2px solid #fff; border-radius: 4px;
|
border: 2px solid #fff; border-radius: 4px;
|
||||||
}
|
}
|
||||||
@@ -167,11 +167,8 @@
|
|||||||
lastY: 0
|
lastY: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function layoutCrop() {
|
function maskSize() {
|
||||||
const stage = document.getElementById('cropStage')
|
const stage = document.getElementById('cropStage')
|
||||||
const img = document.getElementById('cropImage')
|
|
||||||
const mask = document.getElementById('cropMask')
|
|
||||||
if (!stage || !img.naturalWidth) return
|
|
||||||
const sw = stage.clientWidth
|
const sw = stage.clientWidth
|
||||||
const sh = stage.clientHeight
|
const sh = stage.clientHeight
|
||||||
let maskW = sw - 32
|
let maskW = sw - 32
|
||||||
@@ -180,10 +177,31 @@
|
|||||||
maskH = sh - 32
|
maskH = sh - 32
|
||||||
maskW = maskH * COVER_RATIO
|
maskW = maskH * COVER_RATIO
|
||||||
}
|
}
|
||||||
mask.style.width = `${maskW}px`
|
return {
|
||||||
mask.style.height = `${maskH}px`
|
maskW: maskW,
|
||||||
mask.style.left = `${(sw - maskW) / 2}px`
|
maskH: maskH,
|
||||||
mask.style.top = `${(sh - maskH) / 2}px`
|
maskLeft: (sw - maskW) / 2,
|
||||||
|
maskTop: (sh - maskH) / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function minCropScale() {
|
||||||
|
const size = maskSize()
|
||||||
|
return Math.max(size.maskW / cropState.naturalW, size.maskH / cropState.naturalH)
|
||||||
|
}
|
||||||
|
|
||||||
|
function layoutCrop() {
|
||||||
|
const stage = document.getElementById('cropStage')
|
||||||
|
const img = document.getElementById('cropImage')
|
||||||
|
const mask = document.getElementById('cropMask')
|
||||||
|
if (!stage || !img.naturalWidth || !cropState.naturalW) return
|
||||||
|
const size = maskSize()
|
||||||
|
mask.style.right = 'auto'
|
||||||
|
mask.style.bottom = 'auto'
|
||||||
|
mask.style.width = `${size.maskW}px`
|
||||||
|
mask.style.height = `${size.maskH}px`
|
||||||
|
mask.style.left = `${size.maskLeft}px`
|
||||||
|
mask.style.top = `${size.maskTop}px`
|
||||||
|
|
||||||
img.style.width = `${cropState.naturalW * cropState.scale}px`
|
img.style.width = `${cropState.naturalW * cropState.scale}px`
|
||||||
img.style.height = `${cropState.naturalH * cropState.scale}px`
|
img.style.height = `${cropState.naturalH * cropState.scale}px`
|
||||||
@@ -192,15 +210,28 @@
|
|||||||
|
|
||||||
function fitCropImage() {
|
function fitCropImage() {
|
||||||
const stage = document.getElementById('cropStage')
|
const stage = document.getElementById('cropStage')
|
||||||
const maskW = Math.min(stage.clientWidth - 32, (stage.clientHeight - 32) * COVER_RATIO)
|
const scale = minCropScale()
|
||||||
const maskH = maskW / COVER_RATIO
|
|
||||||
const scale = Math.max(maskW / cropState.naturalW, maskH / cropState.naturalH)
|
|
||||||
cropState.scale = scale
|
cropState.scale = scale
|
||||||
cropState.x = (stage.clientWidth - cropState.naturalW * scale) / 2
|
cropState.x = (stage.clientWidth - cropState.naturalW * scale) / 2
|
||||||
cropState.y = (stage.clientHeight - cropState.naturalH * scale) / 2
|
cropState.y = (stage.clientHeight - cropState.naturalH * scale) / 2
|
||||||
layoutCrop()
|
layoutCrop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zoomCrop(factor) {
|
||||||
|
if (!cropState.naturalW) return
|
||||||
|
const size = maskSize()
|
||||||
|
const cx = size.maskLeft + size.maskW / 2
|
||||||
|
const cy = size.maskTop + size.maskH / 2
|
||||||
|
const before = cropState.scale
|
||||||
|
const next = Math.min(Math.max(before * factor, minCropScale()), minCropScale() * 8)
|
||||||
|
if (Math.abs(next - before) < 0.0001) return
|
||||||
|
// Keep the point under the crop center stable while zooming.
|
||||||
|
cropState.x = cx - ((cx - cropState.x) / before) * next
|
||||||
|
cropState.y = cy - ((cy - cropState.y) / before) * next
|
||||||
|
cropState.scale = next
|
||||||
|
layoutCrop()
|
||||||
|
}
|
||||||
|
|
||||||
function openCoverCrop(file, targetInput, hint) {
|
function openCoverCrop(file, targetInput, hint) {
|
||||||
const modal = document.getElementById('cropModal')
|
const modal = document.getElementById('cropModal')
|
||||||
const img = document.getElementById('cropImage')
|
const img = document.getElementById('cropImage')
|
||||||
@@ -270,14 +301,19 @@
|
|||||||
})
|
})
|
||||||
stage.addEventListener('pointerup', () => { cropState.dragging = false })
|
stage.addEventListener('pointerup', () => { cropState.dragging = false })
|
||||||
stage.addEventListener('pointercancel', () => { cropState.dragging = false })
|
stage.addEventListener('pointercancel', () => { cropState.dragging = false })
|
||||||
document.getElementById('cropZoomIn').onclick = () => {
|
document.getElementById('cropZoomIn').onclick = (event) => {
|
||||||
cropState.scale *= 1.12
|
event.preventDefault()
|
||||||
layoutCrop()
|
zoomCrop(1.15)
|
||||||
}
|
}
|
||||||
document.getElementById('cropZoomOut').onclick = () => {
|
document.getElementById('cropZoomOut').onclick = (event) => {
|
||||||
cropState.scale /= 1.12
|
event.preventDefault()
|
||||||
layoutCrop()
|
zoomCrop(1 / 1.15)
|
||||||
}
|
}
|
||||||
|
stage.addEventListener('wheel', (event) => {
|
||||||
|
if (!document.getElementById('cropModal').classList.contains('on')) return
|
||||||
|
event.preventDefault()
|
||||||
|
zoomCrop(event.deltaY > 0 ? 1 / 1.08 : 1.08)
|
||||||
|
}, { passive: false })
|
||||||
document.getElementById('cropCancel').onclick = closeCoverCrop
|
document.getElementById('cropCancel').onclick = closeCoverCrop
|
||||||
document.getElementById('cropConfirm').onclick = async () => {
|
document.getElementById('cropConfirm').onclick = async () => {
|
||||||
const hint = cropState.hint
|
const hint = cropState.hint
|
||||||
|
|||||||
Reference in New Issue
Block a user