65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
|
|
/** 會員頭像:本機選檔 → 壓縮成可存 localStorage 的 data URL */
|
|||
|
|
|
|||
|
|
const MAX_INPUT_BYTES = 5 * 1024 * 1024;
|
|||
|
|
const MAX_EDGE = 256;
|
|||
|
|
const JPEG_QUALITY = 0.86;
|
|||
|
|
|
|||
|
|
function loadImageFromFile(file: File): Promise<HTMLImageElement> {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const url = URL.createObjectURL(file);
|
|||
|
|
const img = new Image();
|
|||
|
|
img.onload = () => {
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
resolve(img);
|
|||
|
|
};
|
|||
|
|
img.onerror = () => {
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
reject(new Error("無法讀取圖片"));
|
|||
|
|
};
|
|||
|
|
img.src = url;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 將本機圖片裁成正方形、縮到 ≤256px,輸出 JPEG data URL。
|
|||
|
|
* mock 階段不真上傳;存進租戶會員紀錄。
|
|||
|
|
*/
|
|||
|
|
export async function fileToMemberAvatarDataUrl(file: File): Promise<string> {
|
|||
|
|
if (!file.type.startsWith("image/")) {
|
|||
|
|
throw new Error("請選擇圖片檔(JPG/PNG/WebP)");
|
|||
|
|
}
|
|||
|
|
if (file.size > MAX_INPUT_BYTES) {
|
|||
|
|
throw new Error("圖片請在 5MB 以內");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const img = await loadImageFromFile(file);
|
|||
|
|
const srcW = img.naturalWidth || img.width;
|
|||
|
|
const srcH = img.naturalHeight || img.height;
|
|||
|
|
if (!srcW || !srcH) throw new Error("無法讀取圖片尺寸");
|
|||
|
|
|
|||
|
|
const side = Math.min(srcW, srcH);
|
|||
|
|
const sx = Math.floor((srcW - side) / 2);
|
|||
|
|
const sy = Math.floor((srcH - side) / 2);
|
|||
|
|
const edge = Math.min(MAX_EDGE, side);
|
|||
|
|
|
|||
|
|
const canvas = document.createElement("canvas");
|
|||
|
|
canvas.width = edge;
|
|||
|
|
canvas.height = edge;
|
|||
|
|
const ctx = canvas.getContext("2d");
|
|||
|
|
if (!ctx) throw new Error("瀏覽器不支援圖片處理");
|
|||
|
|
|
|||
|
|
ctx.imageSmoothingEnabled = true;
|
|||
|
|
ctx.imageSmoothingQuality = "high";
|
|||
|
|
ctx.drawImage(img, sx, sy, side, side, 0, 0, edge, edge);
|
|||
|
|
|
|||
|
|
const dataUrl = canvas.toDataURL("image/jpeg", JPEG_QUALITY);
|
|||
|
|
if (!dataUrl.startsWith("data:image/")) throw new Error("壓縮失敗");
|
|||
|
|
if (dataUrl.length > 400_000) {
|
|||
|
|
// 再壓一輪
|
|||
|
|
const smaller = canvas.toDataURL("image/jpeg", 0.7);
|
|||
|
|
if (smaller.length > 400_000) throw new Error("頭像仍過大,請換較簡單的圖");
|
|||
|
|
return smaller;
|
|||
|
|
}
|
|||
|
|
return dataUrl;
|
|||
|
|
}
|