cover_image

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

千梦凯 Goodme前端团队
2025年03月24日 00:00

一、引言 

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

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

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

二、实现方案 

方案一:CSS3 实现

1. 实现思路

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

2. 实现效果

图片

3. 实现步骤

初始化图片信息

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

export default () => {
constgetBase64 = (img: RcFile, callback: (url: string) => void) => {
    const reader = newFileReader();
    reader.addEventListener('load'() =>callback(reader.resultas string));
    reader.readAsDataURL(img);
  };

consthandleChangeUploadProps['onChange'] = (info) => {
    if (info.file.status === 'uploading') {
      return;
    }
    if (info.file.status === 'done') {
      getBase64(info.file.originFileObjasRcFile(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: 0background: '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}pxscale(${scale}) rotate(${rotate}deg)`,
          }}
        />

      }
  </div>

}

监听图片移动事件

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

  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,
      startMovetrue,
    });
  };

// 使用节流减少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,
        startMovetrue,
      });
    },
    { wait16.7 }
  );

consthandleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
    e.preventDefault();
    setMoveConfig({ ...moveConfig, startMovefalse });
  };

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

图片

图片旋转与放大

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


  <Button icon={<RotateLeftOutlined />} onClick={() => setRotate(rotate - 11.25)} />
  <Button icon={<RotateRightOutlined />} onClick={() => setRotate(rotate + 11.25)} />

图片放大

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

  <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对象,用来上传到服务器

const generateFile = () => {
const container = 图片容器
const canvas = awaithtml2canvas(container, {
    useCORStrue,
    loggingtrue,
    width300,
    height300,
    // 表示开始裁剪的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 = newUint8Array(n);

while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
const file = newFile([u8arr], fileName, { type'png' });

方案二:Canvas Transform实现

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

1. 实现思路

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

2. 实现效果

图片

3. 实现步骤

初始化图片

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

const getBase64 = (img: RcFile, callback: (url: string) => void) => {
const reader = newFileReader();
  reader.addEventListener('load'() =>callback(reader.resultas string));
  reader.readAsDataURL(img);
};

exportdefault () => {
const [imgDOM, setImgDOM] = useState<HTMLImageElement>();
consthandleChangeUploadProps['onChange'] = (info) => {
    if (info.file.status === 'uploading') {
      return;
    }
    if (info.file.status === 'done') {
      setVisible(true);
      getBase64(info.file.originFileObjasRcFile(url) => {
        const img = newImage();
        img.src = url;
        img.onload = () => {
          // 这里缓存一个imgDom,canvas绘制图片需要img元素
          setImgDOM(img);
          // 绘制图片
          ctx.drawImage(imgDOM, 00, img.width,img.height);
        };
      });
    }
  };

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

  );
};

将图片绘制在canvas中心位置

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

图片

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

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的位置以及原点位置,来实现图片的渲染。图片渲染位置也是通更新原点位置来实现图片的截取显示


const ctx = canvasTop.getContext('2d')!;
ctx.clearRect(00,  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方法来进行旋转图片,缩放在采用在绘制的时候,放大图片绘制的比例来进行展示

  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保存图片。

ctx.toDataURL()

三、完善与不足 

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

四、总结 

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


继续滑动看下一个
Goodme前端团队
向上滑动看下一个