前端上传图片处理:缩放、旋转、移动与裁剪

一、引言

在前端开发中,用户对选择的图片进行处理是一个比较常见的功能。对用户选择的图片进行缩放、旋转、移动和裁剪等操作,不仅能够提升用户体验,还能为网页增添更多的互动性和视觉效果。

本文将介绍两种不同的方法来实现这一功能:一种是利用 CSS3 实现图片处理;另一种则是通过Canvas 中的 Transform来完成变换操作。

通过对这两种方案的探讨,读者将能够了解不同方法的优势与特点,并根据实际需求选择最适合的实现方式。

二、实现方案

方案一:CSS3 实现

1.实现思路

利用CSS3 强大的transform 属性对图片进行缩放、旋转和移动操作,通过直观的CSS 属性设置来实现图片变换效果。然后使用html2canvas 库则用于将包含处理后图片的DOM元素转换为Canvas,进而导出为图片,实现对图片的裁剪和最终保存。

2.实现效果

3.实现步骤

初始化图片信息

首先是创建一个静态的图片展示。用来获取用户上传的图片,通过FileReader将图片渲染到页面中。并且绘制一个正方形在图片上方,用于展示图片输出效果

jsx 复制代码
export default () => {
  const getBase64 = (img: RcFile, callback: (url: string) => void) => {
    const reader = new FileReader();
    reader.addEventListener('load', () => callback(reader.result as string));
    reader.readAsDataURL(img);
  };
  
  const handleChange: UploadProps['onChange'] = (info) => {
    if (info.file.status === 'uploading') {
      return;
    }
    if (info.file.status === 'done') {
      getBase64(info.file.originFileObj as RcFile, (url) => {
        setImgUrl(url);
        setVisible(true);
      });
    }
  };
  return <div>
      <Upload
        name="avatar"
        listType="picture-card"
        className="avatar-uploader"
        showUploadList={false}
        onChange={handleChange}
        action={(file) =>
          new Promise((resolve) => {
            getBase64(file as RcFile, (url) => {
              resolve(url);
            });
          })
        }
        >
        <button style={{ border: 0, background: 'none' }} type="button">
          <PlusOutlined />
          <div style={{ marginTop: 8 }}>Upload</div>
        </button>
      </Upload>
    
      <img
          src={imgUrl}
          className="image-container-img"
          style={{
            transform: `translate(${moveConfig.moveX}px, ${moveConfig.moveY}px) scale(${scale}) rotate(${rotate}deg)`,
          }}
        />
      }
  </div>
}

监听图片移动事件

监听图片的鼠标按下事件、鼠标移动事件、鼠标释放事件。通过对比用户初始鼠标位置与鼠标移动位置计算出来图片移动位置,然后设置元素的transform属性,用来移动图片位置。

javascript 复制代码
  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
    // 阻止默认事件,防止触发浏览器默认行为
    e.preventDefault();
    setMoveConfig({
      moveX: moveConfig.moveX,
      moveY: moveConfig.moveY,
      startX: e.clientX - moveConfig.moveX,
      startY: e.clientY - moveConfig.moveY,
      startMove: true,
    });
  };

  // 使用节流减少state更新次数
  const { run: handleMouseMove } = useThrottleFn(
    (e: React.MouseEvent<HTMLDivElement>) => {
      e.preventDefault();
      if (!moveConfig.startMove) {
        return;
      }

      setMoveConfig({
        moveX: e.clientX - moveConfig.startX,
        moveY: e.clientY - moveConfig.startY,
        startX: moveConfig.startX,
        startY: moveConfig.startY,
        startMove: true,
      });
    },
    { wait: 16.7 }
  );

  const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
    e.preventDefault();
    setMoveConfig({ ...moveConfig, startMove: false });
  };

注意:需要使用preventDefault阻止默认动作,否则会引起mouseup事件无法触发,图片会在鼠标弹起后跟上鼠标移动

图片旋转与放大

前面已经监听了鼠标的按下与移动事件,旋转图片相关操作就不能再使用鼠标事件了,我们可以在图片下方加两个渲染按钮,用来记录图片的旋转角度。然后设置图片的transform 属性中的rotate来设置图片的旋转角度。

jsx 复制代码
  <Button icon={<RotateLeftOutlined />} onClick={() => setRotate(rotate - 11.25)} />
  <Button icon={<RotateRightOutlined />} onClick={() => setRotate(rotate + 11.25)} />

图片放大

图片方案采用和旋转相似的方式实现,通过定义一个按钮来控制图片的放大缩小倍数。然后设置图片的transform 中的scale属性来设置图片缩放倍数

jsx 复制代码
  <Button
    icon={<MinusOutlined />}
    disabled={scale <= 0.2}
    onClick={() => {
      setScale(scale - 0.1);
    }}
  />
  <Button
    icon={<PlusOutlined />}
    disabled={scale >= 3}
    onClick={() => {
      setScale(scale + 0.1);
    }}
  />

保存新图片

用户所有的操作都可以通过css3中的transform属性来实现,但是保存图片还是需要用到canvas 。我们可以采用html2canvas库来实现对用户操作之后的图片转换为canvas对象,然后转换为base64 ,再将base64 转换为File对象,用来上传到服务器

javascript 复制代码
const generateFile = () => {
  const container = 图片容器
  const canvas = await html2canvas(container, {
    useCORS: true,
    logging: true,
    width: 300,
    height: 300,
    // 表示开始裁剪的x轴坐标
    x: container.offsetLeft + 150,
    // 表示开始裁剪的y轴坐标
    y: container.offsetTop + 60,
  });
  const dataurl = canvas.toDataURL("image/png");
  const arr = dataurl.split(",");
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  const file = new File([u8arr], fileName, { type: 'png' });
} 

方案二:Canvas Transform实现

使用css3的方案中,主要是直接对图片进行各种处理。相关操作都可以直接用canvas直接实现。不过本文针对canvas的实现方式,将采用对图片上方的形状进行移动、缩放操作、旋转则继续操作图像内容

1.实现思路

直接利用Canvas 的变换矩阵来实现图片的缩放、旋转和移动。通过Canvas 提供的translaterotatescale 等方法,对绘制图片时的变换矩阵进行精确控制,从而实现各种图像变换效果。裁剪操作则通过drawImage方法的参数设置来指定裁剪区域和目标区域。

2.实现效果

3.实现步骤

初始化图片

这一步和上面实现基本一致,先获取用户上传的图片,然后通过CanvasdrawImage将图片渲染到画布中。

jsx 复制代码
const getBase64 = (img: RcFile, callback: (url: string) => void) => {
  const reader = new FileReader();
  reader.addEventListener('load', () => callback(reader.result as string));
  reader.readAsDataURL(img);
};

export default () => {
  const [imgDOM, setImgDOM] = useState<HTMLImageElement>();
  const handleChange: UploadProps['onChange'] = (info) => {
    if (info.file.status === 'uploading') {
      return;
    }
    if (info.file.status === 'done') {
      setVisible(true);
      getBase64(info.file.originFileObj as RcFile, (url) => {
        const img = new Image();
        img.src = url;
        img.onload = () => {
          // 这里缓存一个imgDom,canvas绘制图片需要img元素
          setImgDOM(img);
          // 绘制图片
          ctx.drawImage(imgDOM, 0, 0, img.width,img.height);
        };
      });
    }
  };

  return (
    <Upload onChange={handleChange}>
    </Upload>
  );
};

将图片绘制在canvas中心位置

上面的步骤中,我们将图片绘制到Canvas 中,但是图片是在Canvas 左上角进行绘制的,没有在整个Canvas 中间区域展示。对于将图片居中,我们可以使用Canvas 中的translate方法,将画布的原点定位到画布中间,然后图片渲染的时候起始位置可以设置图片宽高的一半,这样就可以将图片居中了。

因为每次都要重新设置原点,所以推荐每次绘制前先保存Canvas绘制状态,然后绘制完成后恢复上一次状态,可以将画布恢复为初始化状态,避免每次绘制前需要计算初始位置等属性

jsx 复制代码
ctx.save()
ctx.translate(canvas.width / 2, canvas.height / 2);
const { width, height } = imgDOM;
ctx.drawImage(imgDOM, -width / 2, -height / 2, width, height);
// 恢复上一次状态
ctx.restore();

移动可视窗口

示例图中间位置的白色可选区域,通过绘制一个新的Canvas 来展示,在移动的过程中,通过改变小Canvas的位置以及原点位置,来实现图片的渲染。图片渲染位置也是通更新原点位置来实现图片的截取显示

javascript 复制代码
const ctx = canvasTop.getContext('2d')!;
ctx.clearRect(0, 0,  canvasTop.width, canvasTop.height);
ctx.save();
ctx.translate(canvasTop.width / 2 - moveConfig.moveX, canvasTop.height / 2 - moveConfig.moveY);
const { width, height } = imgDOM;
ctx.drawImage(imgDOM, -width / 2, -height / 2, width, height);
ctx.restore();

旋转与缩放Canvas中的图片

旋转直接采用Canvas 中的rotate方法来进行旋转图片,缩放在采用在绘制的时候,放大图片绘制的比例来进行展示

jsx 复制代码
  ctx.save();
  ctx.translate(canvas.width / 2, canvas.height / 2);
 // 新增旋转相关
  ctx.rotate((rotate * Math.PI) / 180);
  const { width, height } = imgDOM;
 // 实际宽度
  const realWidth = width * scale;
  // 实际高度
  const realHeight = height * scale;
  ctx.drawImage(imgDOM, -realWidth / 2, -realHeight / 2, realWidth, realHeight);
  ctx.restore();

预览图片

和移动窗口图片一样,直接使用一个Canvas来展示

保存图片

保存图片时,可以直接使用右侧效果图直接进行toDataURL保存图片。

javascript 复制代码
ctx.toDataURL()

三、完善与不足

本文只是简单的实现了两用方案来处理图片,有很多细节点没有着重实现。比如CSS3方案中图片移动限制,取景框可能没有图片;鼠标再离开图片后抬起,导致无法关闭图片移动;Canvas中取景框不能调整大小;缩放,旋转不能自定义大小与角度。未完待续。

四、总结

本文使用了两种方案来实现图片裁切、旋转、缩放操作。一种是操作DOM 元素的方式,一种通过Canvas操作方式。 两种方案各有优劣,开发者可根据实现难度、性能表现、跨域处理需求、输出质量以及移动端支持等多方面因素综合考虑,选择合适的方案。

相关推荐
古茗前端团队16 天前
前端视角下的图像格式进化论:从像素战争到性能革命
图片资源
爱丶不疚1 个月前
用过Blurhash渐进式加载图片吗?没有的话我劝你慎用
前端·性能优化·图片资源
老A的AI实验室3 个月前
赛博周刊·2024年度工具精选(图片资源类)
人工智能·ai·llm·大语言模型·图片资源·genai
冒泡的肥皂9 个月前
用java做个图片处理网站过程遇到的坑
后端·mvc·图片资源
pycode9 个月前
使用Python生成图片验证码
python·图片资源
字节跳动技术团队10 个月前
抖音Android端图片优化实践
性能优化·图片资源
Miraitowa00710 个月前
加载图片的三种流行且有效的方式
前端·图片资源
幕呈10 个月前
【译】<img> 的演变:没有 GIF 的 Gif
图片资源
言淦1 年前
做了一个图片批量压缩小工具: tiny_gradio
后端·python·图片资源