62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
/**
|
||
* 图片压缩工具 —— 头像上传前的前端预压缩,降低网络延迟
|
||
*
|
||
* 头像展示尺寸很小(会话列表 38px、成员/消息 30px、编辑预览 96px),
|
||
* 上传前压成 AVATAR_UPLOAD_SIZE 见方的 JPEG,可显著减少请求体体积。
|
||
*/
|
||
|
||
/** 头像上传目标边长(正方形,px) */
|
||
export const AVATAR_UPLOAD_SIZE = 50
|
||
|
||
/** 载入图片为可绘制源(优先 ImageBitmap,回退 HTMLImageElement) */
|
||
async function LoadImageSource(file: File): Promise<ImageBitmap | HTMLImageElement> {
|
||
if (typeof createImageBitmap === 'function') {
|
||
return await createImageBitmap(file)
|
||
}
|
||
const url = URL.createObjectURL(file)
|
||
try {
|
||
return await new Promise<HTMLImageElement>((resolve, reject) => {
|
||
const el = new Image()
|
||
el.onload = () => resolve(el)
|
||
el.onerror = () => reject(new Error('图片解码失败'))
|
||
el.src = url
|
||
})
|
||
} finally {
|
||
URL.revokeObjectURL(url)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 将图片居中裁剪为正方形并缩放为 Size×Size 的 JPEG File。
|
||
*
|
||
* - 透明区域以白色填充(JPEG 不支持透明,避免出现黑底)
|
||
* - 非图片、canvas 不可用或解码失败时原样返回,保证上传不中断
|
||
*/
|
||
export async function compressImageSquare(file: File, Size = AVATAR_UPLOAD_SIZE): Promise<File> {
|
||
if (!file.type.startsWith('image/')) return file
|
||
try {
|
||
const source = await LoadImageSource(file)
|
||
const canvas = document.createElement('canvas')
|
||
canvas.width = Size
|
||
canvas.height = Size
|
||
const ctx = canvas.getContext('2d')
|
||
if (!ctx) return file
|
||
// 居中裁剪正方形后缩放
|
||
const side = Math.min(source.width, source.height)
|
||
const sx = (source.width - side) / 2
|
||
const sy = (source.height - side) / 2
|
||
ctx.fillStyle = '#ffffff'
|
||
ctx.fillRect(0, 0, Size, Size)
|
||
ctx.imageSmoothingEnabled = true
|
||
ctx.imageSmoothingQuality = 'high'
|
||
ctx.drawImage(source, sx, sy, side, side, 0, 0, Size, Size)
|
||
if (typeof ImageBitmap !== 'undefined' && source instanceof ImageBitmap) source.close()
|
||
const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.9))
|
||
if (!blob) return file
|
||
const base = file.name.replace(/\.[^.]+$/, '')
|
||
return new File([blob], `${base}.jpg`, { type: 'image/jpeg', lastModified: Date.now() })
|
||
} catch {
|
||
return file
|
||
}
|
||
}
|