【项目】前端图片裁剪

2021-05-13 10:39:47 浏览数 (1)

把工作中做过的一些小东西或者功能总结记录,分享学习

最近做了一个需求,是做 视频封面裁剪的,涉及到的一个功能点是 自动裁剪,就是拿到一张图片,自动裁剪 图片的中间区域成 一个正方形

其实这个挺简单的,说到前端裁剪,无非就是使用 canvas ,但是也避免不了会忘记其中的细节,所以要写文章记录一下

1 api 简单介绍

没错,用的就是 canvas.drawImage 这个 api 完成我们的截图功能,看起来好像没有涉及到什么复杂的东西

但是实际上也的确没有什么复杂的东西,只不过使用的时候会难以避免碰到一些坑而已

兼容性

canvas.drawImage 这个方法 的兼容性我们再来看一下,现在基本大部分浏览器已经兼容了,就除了 IE6-8 吧

但是据我们公司统计,IE的使用人数都几乎为0了

IE9 :0.0%

IE10:0.1%

IE11:0.1%

IE9 都统计为0了,那以下的几乎就可以忽略不计了,所以大胆放心使用

成功率

如果你可能还是会有些不放心,心里总是好像没有什么底,不知道在别人电脑手机是否能正常展现

我们对这个功能也做了监控,截图的成功率高达99.5%!

所以我们大可以放心使用这个 drawImage 进行我们的前端截图

2 api 详细介绍

drawImage 的 参数还是挺多的,挺容易弄混的,所以这里必须要花大力气写清楚,反正每次用都是要看一次的

我也没想着能够一劳永逸

先来看下这个完整的 api(其实他的参数有很多个用法,这里只介绍我们这里截图用到的)

里面涉及的是 图片的 位置 宽高, canvas 的 位置宽高

这几个参数如果不仔细想一下的话,是有一点弄混的第一个参数,imgObj,就是 Image 生成的实例

后面四个参数,表示的就是图片的 位置,宽高 信息

以你的图片为底图,以 imgX 和imgY 找到起始点,然后再以你想要的宽高裁出大小

比如这样一张图片,红色区域就是我们裁剪出来的地方

紧接着,最后四个参数,表示的就是 canvas 画布的 位置,宽高信息

为什么需要这四个参数?

在上面四个图片参数中,我们已经裁减出了我们需要的图片部分

我们要把图片放到 canvas 上,所以我们需要确定 我们要放在哪里啊!!

第一,要知道放置的起点,所以有了 canvasX,canvasY

第二,要知道绘制的大小(用于缩放)

虽然我们已经有了裁减出来的图片大小,但是我们也要确定该图片在 canvas 上绘制多大

可以以此来完成缩放功能,如果你想原样绘制在上面,那么你就大小设置成 裁减的大小就ok

步骤就相当于是

先裁减图片,然后再绘制到 canvas 上

3 裁减中间区域

好了,上面我们介绍完了,就说这次我们的需求了,就是要裁减图片中间区域因为我们在用户上传图片做封面的时候,图片是用户上传的五花八门的图片

所以我们需要首先自动裁减成正方形做成封面,比如这样

如果是宽>高,那么就 高占满

如果是 高> 宽,那么就 宽占满

所以我们需要获取到以下这些数据

1、图片的 原始宽高

2、裁减的图片位置

3、裁减的图片大小

首先拿到 图片原始宽高,比较一下是 更长 还是更高,从而确定裁减的 大小

高>宽,裁减的宽高= 图片的宽

宽>高,裁减的宽高= 图片的高

知道了裁减的宽高之后,就可以知道裁减的起始位置

像这样

让我们看一下大概的代码

代码语言:javascript复制
const image = new Image();

碰到的问题

1、图片跨域问题

当我们使用 canvas 导出图片的时候,如果图片对象没有设置可以跨域,那么就会报错

如果在新建 Image 对象的时候,如果加上 跨域属性

代码语言:javascript复制
image.crossOrigin = "anonymous";

有时候设置了属性还是一样会报错,可能是命中了缓存,所以我们最好还要在 图片访问路径加一个时间戳

代码语言:javascript复制
img.src = `xxxx?time=${Date.now()}`;

4 详细代码

但是实际使用中,需要处理更多的问题,并且要封装更加通用一些,我大概分了三个方法

imgUpload,用来新建图片示例

getImageCutArea,根据图片url 和位置 裁减出想要的区域

getImageCenterArea,根据图片url 和比例,裁减出中间区域

所以在这里我们只需要直接调用 getImageCenterArea,传入一个url 就可以了,就会返回裁剪好的base64

代码语言:javascript复制
function imgUpload(url) {  



  const image = new Image();

  image.crossOrigin = "anonymous";  



  return new Promise((resolve, reject) => {    



    const loaded = (event) => {

      image.onload = null;
      resolve(        

         Object.assign(event, {          

         naturalWidth: image.naturalWidth,          

         naturalHeight: image.naturalHeight,          

         imageObj: image,

        })
      );
    };    



    const errored = (event) => {

      image.onerror = null;      

      console.log("img 加载error", event);

      reject(event);
    };

    image.onload = loaded;
    image.onerror = errored;
    image.onabort = errored;

    

    // 如果路径是 base64 就不用加上时间戳,

            如果是http,防止跨域报错所以加上 time

    image.src =
      url?.indexOf?.("data:image") > -1 ?

        url :

        `${url}?time=${Date.now()}`;

  });
}



async function getImageCutArea(url, position) {  



  let imgInfo = await imgUpload(url);  

  const { imageObj } = imgInfo;  

  const { x, y, width, height } = position;  



  const canvas = document.createElement("canvas");

  canvas.width = width;
  canvas.height = height;  

  const ctx = canvas.getContext("2d");



  ctx.drawImage(

    imageObj,

    x, y, width, height,

    0, 0, width, height

  );



  try {    

    const dataUrl = canvas.toDataURL("image/jpeg");    

    return Promise.resolve(dataUrl);

  } catch (e) {    

    return Promise.reject(e);

  }
}



async function getImageCenterArea(url, aspect = 1) {  



  const imgInfo = await imgUpload(url);  

  const { naturalWidth, naturalHeight } = imgInfo;  

  const imgHeight = naturalHeight;  

  const imgWidth = naturalWidth;  



  const imgRatio = imgWidth / imgHeight;  

  const isImgMoreHigh = imgRatio < aspect;  

  let width = 0;  

  let height = 0;  



  // 如果图片更高,那么默认宽度100%占满。

        否则宽度就是 以 aspect 为准占满高度后的 适配宽度

  if (isImgMoreHigh) {
    width = imgWidth;
    height = imgWidth / aspect;
  } else if (!isImgMoreHigh) {
    height = imgHeight;
    width = imgHeight * aspect;
  }  



  const y = (imgHeight - height) / 2;  

  const x = (imgWidth - width) / 2;  

  const result = await getImageCutArea(imgInfo, {

    x,
    y,
    width,
    height,
  });



  return result;

}

最后

鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵, 如果有任何描述不当的地方,欢迎后台联系本人。

0 人点赞