Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions js/image-viewer/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { positiveAdd, positiveSubtract } from '../utils/math';
import type { ZoomOptions, ZoomResult, TranslateOffset, ImageScale } from './types';
import { DEFAULT_IMAGE_SCALE } from './types';

export type { ZoomOptions, ZoomResult, TranslateOffset, ImageScale };
export { DEFAULT_IMAGE_SCALE };

/**
* 检测图片是否超出视口(容器)边界
* @param container 外层容器元素
* @param modalBox 图片包裹元素
*/
export const isImageExceedsViewport = (container: HTMLElement, modalBox: HTMLElement): boolean => {
const containerRect = container.getBoundingClientRect();
const modalRect = modalBox.getBoundingClientRect();
return (
modalRect.left < containerRect.left ||
modalRect.right > containerRect.right ||
modalRect.top < containerRect.top ||
modalRect.bottom > containerRect.bottom
);
};

/** 镜像默认值(未镜像) */
export const MIRROR_DEFAULT = 1;

/** 切换镜像状态:1 → -1,-1 → 1 */
export const toggleMirror = (current: number): number => (current > 0 ? -1 : 1);

/** 每次旋转的角度(逆时针 90°) */
export const ROTATE_DEG = -90;

/**
* 计算最短路径归零的旋转补偿值
* 用于 resetRotate 场景:避免 CSS transition 时图片"倒转一大圈"
*
* @param currentDeg 当前累计旋转角度
* @returns 需要减去的补偿值(rotate.value -= 返回值 即可归零)
*
* @example
* // currentDeg = -270 → return 90(-270 - 90 = -360 ≡ 0°)
* // currentDeg = -180 → return -180(-180 - (-180) = 0)
* // currentDeg = 0 → return 0(无需旋转)
*/
export function calcResetRotation(currentDeg: number): number {
const degreeToRotate = currentDeg % 360;
if (degreeToRotate === 0) return 0;
// 找最短方向旋转回 0°
return Math.abs(degreeToRotate) > 180 ? (degreeToRotate + 360) % 360 : degreeToRotate;
}

/** 将缩放值限制在 [min, max] 范围内 */
export function clampScale(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}

/**
* 计算放大后的新 scale 值(使用精确浮点数加法)
* @returns clamp 后的新 scale 值
*/
export function calcZoomInScale(oldScale: number, step: number, min: number, max: number): number {
return clampScale(positiveAdd(oldScale, step), min, max);
}

/**
* 计算缩小后的新 scale 值(使用精确浮点数减法)
* @returns clamp 后的新 scale 值
*/
export function calcZoomOutScale(oldScale: number, step: number, min: number, max: number): number {
return clampScale(positiveSubtract(oldScale, step), min, max);
}

/**
* 计算缩放后的位移补偿
* 公式:newTranslate = scaleRatio * T + (1 - scaleRatio) * Z
* 其中 Z 为缩放中心,T 为当前位移,scaleRatio = newScale / oldScale
*/
export function calculateTranslateOffset(
oldScale: number,
newScale: number,
options?: ZoomOptions
): TranslateOffset | undefined {
if (options?.mouseOffsetX == null || options?.mouseOffsetY == null) {
return undefined;
}

const scaleRatio = newScale / oldScale;
const { translateX = 0, translateY = 0 } = options?.currentTranslate ?? {};
const { mouseOffsetX, mouseOffsetY } = options;

return {
translateX: scaleRatio * translateX + (1 - scaleRatio) * mouseOffsetX,
translateY: scaleRatio * translateY + (1 - scaleRatio) * mouseOffsetY,
};
}

/**
* 执行一次 zoom in 并计算位移补偿
* @returns { newScale, zoomResult }
*/
export function zoomIn(
oldScale: number,
step: number,
min: number,
max: number,
options?: ZoomOptions
): { newScale: number; zoomResult: ZoomResult } {
const newScale = calcZoomInScale(oldScale, step, min, max);
return {
newScale,
zoomResult: { newTranslate: calculateTranslateOffset(oldScale, newScale, options) },
};
}

/**
* 执行一次 zoom out 并计算位移补偿
* @returns { newScale, zoomResult }
*/
export function zoomOut(
oldScale: number,
step: number,
min: number,
max: number,
options?: ZoomOptions
): { newScale: number; zoomResult: ZoomResult } {
const newScale = calcZoomOutScale(oldScale, step, min, max);
return {
newScale,
zoomResult: { newTranslate: calculateTranslateOffset(oldScale, newScale, options) },
};
}
8 changes: 0 additions & 8 deletions js/image-viewer/types.d.ts

This file was deleted.

50 changes: 50 additions & 0 deletions js/image-viewer/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export interface ImageInfo {
mainImage: string | File;
thumbnail?: string | File;
download?: boolean;
isSvg?: boolean;
}

export type Images = Array<string | File | ImageInfo>;

/** 位移偏移量 */
export interface TranslateOffset {
translateX: number;
translateY: number;
}

/** 缩放选项 */
export interface ZoomOptions {
/** 缩放中心点 X 坐标(相对于预览图片容器中心的偏移量) */
mouseOffsetX?: number;
/** 缩放中心点 Y 坐标(相对于预览图片容器中心的偏移量) */
mouseOffsetY?: number;
/** 当前位移 */
currentTranslate?: TranslateOffset;
}

/** 缩放结果 */
export interface ZoomResult {
/** 缩放后的新位移 */
newTranslate?: TranslateOffset;
}

/** 图片缩放配置 */
export interface ImageScale {
/** 缩放的最大比例 */
max: number;
/** 缩放的最小比例 */
min: number;
/** 缩放的步长速度 */
step: number;
/** 默认的缩放比例 */
defaultScale: number;
}

/** ImageScale 的默认值,所有使用处应引用此常量,避免多处声明不一致 */
export const DEFAULT_IMAGE_SCALE: ImageScale = {
max: 2,
min: 0.5,
step: 0.2,
defaultScale: 1,
};
5 changes: 5 additions & 0 deletions js/utils/math.ts
Comment thread
RSS1102 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* 通用数学精度工具函数
* 从 input-number/number.ts 中提取出来,供跨组件使用
*/
export { positiveAdd, positiveSubtract } from '../input-number/number';
Loading