@ -523,20 +523,25 @@ const handleImageLoad = () => {
* 裁剪图片
* @ param { 'image/jpeg' | 'image/png' } format - 输出图片格式
* @ param { number } quality - 压缩质量 ( 0 - 1 )
* @ param { 'blob' | 'base64' } outputType - 输出类型
* @ param { number } targetWidth - 目标宽度 ( 可选 , 不传则为原始裁剪宽度 )
* @ param { number } targetHeight - 目标高度 ( 可选 , 不传则为原始裁剪高度 )
* /
const getCropImage = async (
format : 'image/jpeg' | 'image/png' = 'image/jpeg' ,
quality : number = 0.92 ,
outputType : 'base64' | 'blob' = 'blob' ,
targetWidth ? : number ,
targetHeight ? : number ,
) : Promise < string | undefined > => {
) : Promise < Blob | string | undefined > => {
if ( ! props . img || ! bgImageRef . value || ! containerRef . value ) return ;
/ / 质 量 参 数 边 界 修 正 : 强 制 限 制 在 0 - 1 区 间 , 防 止 传 入 非 法 值 报 错
const validQuality = Math . max ( 0 , Math . min ( 1 , quality ) ) ;
/ / 创 建 临 时 图 片 对 象 获 取 原 始 尺 寸
const tempImg = new Image ( ) ;
/ / O n l y s e t c r o s s O r i g i n f o r c r o s s - o r i g i n U R L s t h a t n e e d C O R S
/ / 跨 域 图 片 处 理 : 仅 对 非 同 源 的 网 络 图 片 设 置 跨 域 匿 名
if ( props . img . startsWith ( 'http://' ) || props . img . startsWith ( 'https://' ) ) {
try {
const url = new URL ( props . img ) ;
@ -544,7 +549,7 @@ const getCropImage = async (
tempImg . crossOrigin = 'anonymous' ;
}
} catch {
/ / I n v a l i d U R L , p r o c e e d w i t h o u t c r o s s O r i g i n
/ / I n v a l i d U R L , 跳 过 跨 域 配 置 , 不 中 断 执 行
}
}
@ -553,7 +558,7 @@ const getCropImage = async (
const timeout = setTimeout ( ( ) => {
tempImg . removeEventListener ( 'load' , handleLoad ) ;
tempImg . removeEventListener ( 'error' , handleError ) ;
reject ( new Error ( '图片加载超时' ) ) ;
reject ( new Error ( '图片加载超时,超时时间10秒 ' ) ) ;
} , 10 _000 ) ;
const handleLoad = ( ) => {
clearTimeout ( timeout ) ;
@ -571,7 +576,6 @@ const getCropImage = async (
tempImg . addEventListener ( 'load' , handleLoad ) ;
tempImg . addEventListener ( 'error' , handleError ) ;
tempImg . src = props . img ;
} ) ;
@ -595,11 +599,11 @@ const getCropImage = async (
const cropOnImgX = cropLeft - imgOffsetX ;
const cropOnImgY = cropTop - imgOffsetY ;
/ / 4 . 计 算 渲 染 图 片 到 原 始 图 片 的 缩 放 比 例 ( 关 键 : 保 留 原 始 像 素 )
/ / 4 . 计 算 渲 染 图 片 到 原 始 图 片 的 缩 放 比 例 ( 保 留 原 始 像 素 )
const scaleX = tempImg . width / renderedImgWidth ;
const scaleY = tempImg . height / renderedImgHeight ;
/ / 5 . 映 射 到 原 始 图 片 的 裁 剪 区 域 ( 精 确 到 原 始 像 素 )
/ / 5 . 映 射 到 原 始 图 片 的 裁 剪 区 域 ( 精 确 到 原 始 像 素 , 防 止 越 界 )
const originalCropX = Math . max ( 0 , Math . floor ( cropOnImgX * scaleX ) ) ;
const originalCropY = Math . max ( 0 , Math . floor ( cropOnImgY * scaleY ) ) ;
const originalCropWidth = Math . min (
@ -611,27 +615,32 @@ const getCropImage = async (
tempImg . height - originalCropY ,
) ;
/ / 6 . 处 理 高 清 屏 适 配 ( 关 键 : 解 决 R e t i n a 屏 模 糊 )
/ / 边 界 校 验 : 裁 剪 尺 寸 非 法 则 返 回
if ( originalCropWidth <= 0 || originalCropHeight <= 0 ) return ;
/ / 6 . 处 理 高 清 屏 适 配 ( 解 决 R e t i n a 屏 模 糊 )
const dpr = window . devicePixelRatio || 1 ;
/ / 最 终 画 布 尺 寸 ( 优 先 使 用 原 始 裁 剪 尺 寸 , 或 目 标 尺 寸 )
const finalWidth = targetWidth || originalCropWidth ;
const finalHeight = targetHeight || originalCropHeight ;
/ / 最 终 画 布 尺 寸 ( 优 先 使 用 传 入 的 目 标 尺 寸 , 无 则 用 原 始 裁 剪 尺 寸 )
const finalWidth = targetWidth ? Math . max ( 1 , targetWidth ) : originalCropWidth ;
const finalHeight = targetHeight
? Math . max ( 1 , targetHeight )
: originalCropHeight ;
/ / 创 建 画 布 ( 乘 以 设 备 像 素 比 , 保 证 高 清 )
/ / 创 建 画 布 并 获 取 绘 制 上 下 文
const canvas = document . createElement ( 'canvas' ) ;
const ctx = canvas . getContext ( '2d' ) ;
if ( ! ctx ) return ;
/ / 画 布 物 理 尺 寸 ( 适 配 高 清 屏 )
/ / 画 布 物 理 尺 寸 ( 乘 以 设 备 像 素 比 , 保 证 高 清 无 模 糊 )
canvas . width = finalWidth * dpr ;
canvas . height = finalHeight * dpr ;
/ / 画 布 显 示 尺 寸 ( 视 觉 尺 寸 )
/ / 画 布 显 示 尺 寸 ( 视 觉 尺 寸 , 和 最 终 展 示 一 致 )
canvas . style . width = ` ${ finalWidth } px ` ;
canvas . style . height = ` ${ finalHeight } px ` ;
/ / 缩 放 上 下 文 ( 适 配 D P R )
/ / 缩 放 画 布 上 下 文 , 适 配 高 清 屏 D P R
ctx . scale ( dpr , dpr ) ;
/ / 7 . 绘 制 裁 剪 后 的 图 片 ( 使 用 原 始 像 素 绘 制 , 保 证 清 晰 度 )
@ -647,8 +656,22 @@ const getCropImage = async (
finalHeight , / / 画 布 绘 制 高 度 ( 目 标 尺 寸 )
) ;
/ / 8 . 导 出 图 片 ( 指 定 质 量 , 平 衡 清 晰 度 和 体 积 )
return canvas . toDataURL ( format , quality ) ;
try {
return outputType === 'base64'
? canvas . toDataURL ( format , validQuality )
: new Promise < Blob > ( ( resolve ) => {
canvas . toBlob (
( blob ) => {
/ / 兜 底 : 如 果 b l o b 生 成 失 败 , 返 回 空 B l o b ( 防 止 n u l l )
resolve ( blob || new Blob ( [ ] , { type : format } ) ) ;
} ,
format ,
validQuality ,
) ;
} ) ;
} catch ( error ) {
console . error ( '图片导出失败:' , error ) ;
}
} ;
/ / 监 听 比 例 变 化 , 重 新 调 整 裁 剪 框