CSS 中操作移动,缩放和旋转

基于贴纸的功能:

  • 对贴纸进行移动,缩放和旋转的操作

在之前的文章中,我们讲解了 H5 Canvas 中操作移动,缩放和旋转

但是,如果贴纸过多的话,我们在一个 Canvas 中绘制,会出现多余的绘制问题。那么,解决这个问题,我们可以有一些思路来优化:

  1. 使用多个 Canvas。是可以解决多次绘制的问题,但是,缩放或者旋转可能会改变贴纸在 canvas 的展示,比如溢出的部分绘制不出来。
  2. 我们可以使用 css 来实现。通过 transform 属性。为什么不直接改变 WIDTH & HEIGHTTOP & LEFT 等属性呢?因为 transform 性能更好。

下面,我们来实现下标题中的功能👇

这里,我们使用 React 来实现下,相关的 HTML 结构如下:

typescript 复制代码
{
  stickerData.map((item, index) => {
    return (
      <div
        key={index}
        style={{
          width: item.size.width + "px",
          height: item.size.height + "px",
          zIndex: index,
        }}
        ref={(ref) => {
          item.stickerElement = ref;
        }}
        onMouseDown{(e) => {
          handleMouseDown(e, item);
        }}
        onMouseMove{(e) => {
          handleMove(e, item);
        }}
        onMouseUp={handleMoveEnd}
        onMouseLeave{handleMoveEnd}
        onWheel={(e) => {
          // 滚轮的滚动模拟缩放
          const scale = 0.1;
          handleZoom(1 + (e.deltaY > 0 ? -1 : 1) * scale, item);
        }}
      >
        Sticker
      </div>
    );
  });
}

解决方案

当鼠标点击的时候,我们记录下相关的坐标。

typescript 复制代码
const handleMouseDown = (event: { clientX: number, clientY: number }, sticker: IStickerData) => {
  state.isMoveLock = false;
  state.startClientX = event.clientX;
  state.startClientY = event.clientY;
  
  state.modelX = sticker.location.x; // 记录相关的偏移量 x
  state.modelY = sticker.location.y; // 记录相关的偏移量 y
  
  // 开始处理偏移函数,这个后面说
}

结束移动的时候,我们得把相关的标识符调整状态。

typescript 复制代码
const handleMoveEnd = React.useCallback(() => {
  state.isMoveLocak = true; // 锁定移动
  state.initDistance = -1; // 移动的距离,用来监听手势的移动的距离
})

我们使用鼠标滚轮进行缩放:

typescript 复制代码
const handleZoom = React.useCallback((scale: number, sticker: IStickerData) => {
  const width = sticker.size.width * state.initScale * scale;
  
  const stickerScale = width / sticker.size.widht;
  
  sticker.scale = stickerScale;
  
  // 开始处理偏移函数
  handleEachStickerElement(sticker);
})

重点是处理的偏移的函数 👇

typescript 复制代码
const handleEachStickerElement  = (sticker: IStickerData) => {
  // 核心
  sticker.$stickerElement.style.transform = `translate(${sticker.location.x}px, ${sticker.location.y}px) scale(${sticker.scale}) rotate(${sticker.rotate}deg)`
}

细心的读者会发现,我们这里并没有 rotate 的触发操作,我们可以添加额外的配置,比如在页面的固定位置触发旋转操作

后话

我们可以对贴纸的移动,缩放和旋转做以下的优化:

  1. 我们都是基于鼠标的控制,如果我们需要在触屏设备上处理的话。我们可以添加下面的方法👇
typescript 复制代码
<div
  onTouchStart={(e) => {
    const touchList: React.Touch[] = []; // 获取触发的列表
    // other
    handleMouseDown(touchList[touchList.length - 1], item);
  }}
  onTouchMove{(e) => {
    const touchList: React.Touch[] = [];
    // other
    if (touchList.length === 1 || touchList.length > 2) {
        handleMove(touchList[touchList.length - 1], item);
    } else if (touchList.length === 2) {
        if (state.initDistance < 0) {
            state.initDistance = twoTouchDistance(touchList); // 计算两个点的距离
        } else {
            const distance = twoTouchDistance(touchList);
            handleZoom(distance / state.initDistance, item); // 缩放功能
        }
    }
  }}
  onTouchEnd={handleMoveEnd}
>
  Sticker
</div>
  1. 我们可以对贴纸的移动范围进行限制。我们可以在函数 handleEachStickerElement 上进行修改👇
typescript 复制代码
// 限制贴纸范围
const limitStickerMoveRatioEtc = {
    scale: { min: 0.5, max: 2 },
    // other
};

type limitStickerMoveRatioEtcType = typeof limitStickerMoveRatioEtc;

const handleEachStickerElement = (sticker: IStickerData, limitRatioEtc: limitStickerMoveRatioEtcType = limitStickerMoveRatioEtc) {
   // 1. 如果不存在贴纸的元素,则进行处理,返回不操作并进行日志记录
   // 2. 限定移动和缩放的范围,上面的 limitStickerMoveRatioEtc 允许贴纸缩小到原来的一半,只能放大到原来的两倍
   // 3. 缓存贴纸对应的数据(缩放,移动和旋转)
}

参考

相关推荐
小小小小宇1 小时前
ESLint 插件笔记
前端
纪伊路上盛名在1 小时前
jupyter内核崩溃
前端·数据库·jupyter·生物信息·基因组·k-mer
Net蚂蚁代码3 小时前
Angular入门的环境准备步骤工作
前端·javascript·angular.js
小着5 小时前
vue项目页面最底部出现乱码
前端·javascript·vue.js·前端框架
lichenyang4538 小时前
React ajax中的跨域以及代理服务器
前端·react.js·ajax
呆呆的小草8 小时前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
一 乐9 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
testleaf9 小时前
前端面经整理【1】
前端·面试
好了来看下一题9 小时前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron
啃火龙果的兔子9 小时前
前端八股文-react篇
前端·react.js·前端框架