基于贴纸的功能:
- 对贴纸进行移动,缩放和旋转的操作
在之前的文章中,我们讲解了 H5 Canvas 中操作移动,缩放和旋转。
但是,如果贴纸过多的话,我们在一个 Canvas
中绘制,会出现多余的绘制问题。那么,解决这个问题,我们可以有一些思路来优化:
- 使用多个
Canvas
。是可以解决多次绘制的问题,但是,缩放或者旋转可能会改变贴纸在canvas
的展示,比如溢出的部分绘制不出来。 - 我们可以使用
css
来实现。通过transform
属性。为什么不直接改变WIDTH & HEIGHT
,TOP & 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
的触发操作,我们可以添加额外的配置,比如在页面的固定位置触发旋转操作
后话
我们可以对贴纸的移动,缩放和旋转做以下的优化:
- 我们都是基于鼠标的控制,如果我们需要在触屏设备上处理的话。我们可以添加下面的方法👇
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>
- 我们可以对贴纸的移动范围进行限制。我们可以在函数
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. 缓存贴纸对应的数据(缩放,移动和旋转)
}