web(avatar-crop): fix narrow-image centering and add circular tg-like mask
All checks were successful
CI / test (push) Successful in 39s
All checks were successful
CI / test (push) Successful in 39s
This commit is contained in:
@@ -12,6 +12,7 @@ const VIEWPORT_SIZE = 320;
|
|||||||
const OUTPUT_SIZE = 500;
|
const OUTPUT_SIZE = 500;
|
||||||
const MIN_ZOOM = 1;
|
const MIN_ZOOM = 1;
|
||||||
const MAX_ZOOM = 4;
|
const MAX_ZOOM = 4;
|
||||||
|
const MASK_RATIO = 0.88;
|
||||||
|
|
||||||
export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropModalProps) {
|
export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropModalProps) {
|
||||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||||
@@ -162,13 +163,13 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
|
|||||||
<div className="absolute inset-0 flex items-center justify-center p-4" onClick={(event) => event.stopPropagation()}>
|
<div className="absolute inset-0 flex items-center justify-center p-4" onClick={(event) => event.stopPropagation()}>
|
||||||
<div className="w-full max-w-lg rounded-2xl border border-slate-700/80 bg-slate-900/95 p-4 shadow-2xl">
|
<div className="w-full max-w-lg rounded-2xl border border-slate-700/80 bg-slate-900/95 p-4 shadow-2xl">
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<p className="text-sm font-semibold text-slate-100">Crop avatar</p>
|
<p className="text-sm font-semibold text-slate-100">Drag to reposition</p>
|
||||||
<button className="rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 disabled:opacity-60" disabled={processing} onClick={onCancel} type="button">
|
<button className="rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 disabled:opacity-60" disabled={processing} onClick={onCancel} type="button">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-auto mb-3 h-[320px] w-[320px] overflow-hidden rounded-xl border border-slate-700 bg-slate-950">
|
<div className="mx-auto mb-3 h-[320px] w-[320px] overflow-hidden rounded-2xl border border-slate-700 bg-slate-950">
|
||||||
<div
|
<div
|
||||||
className="relative h-full w-full cursor-grab select-none active:cursor-grabbing"
|
className="relative h-full w-full cursor-grab select-none active:cursor-grabbing"
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
@@ -193,6 +194,12 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
<div
|
||||||
|
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-slate-200/70 shadow-[0_0_0_9999px_rgba(15,23,42,0.58)]"
|
||||||
|
style={{ width: `${Math.floor(VIEWPORT_SIZE * MASK_RATIO)}px`, height: `${Math.floor(VIEWPORT_SIZE * MASK_RATIO)}px` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -229,10 +236,16 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clampPosition(pos: { x: number; y: number }, width: number, height: number) {
|
function clampPosition(pos: { x: number; y: number }, width: number, height: number) {
|
||||||
const minX = Math.min(0, VIEWPORT_SIZE - width);
|
if (width <= VIEWPORT_SIZE && height <= VIEWPORT_SIZE) {
|
||||||
const maxX = 0;
|
return {
|
||||||
const minY = Math.min(0, VIEWPORT_SIZE - height);
|
x: (VIEWPORT_SIZE - width) / 2,
|
||||||
const maxY = 0;
|
y: (VIEWPORT_SIZE - height) / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const minX = width <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - width) / 2 : VIEWPORT_SIZE - width;
|
||||||
|
const maxX = width <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - width) / 2 : 0;
|
||||||
|
const minY = height <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - height) / 2 : VIEWPORT_SIZE - height;
|
||||||
|
const maxY = height <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - height) / 2 : 0;
|
||||||
return {
|
return {
|
||||||
x: Math.max(minX, Math.min(maxX, pos.x)),
|
x: Math.max(minX, Math.min(maxX, pos.x)),
|
||||||
y: Math.max(minY, Math.min(maxY, pos.y)),
|
y: Math.max(minY, Math.min(maxY, pos.y)),
|
||||||
|
|||||||
Reference in New Issue
Block a user