Canvas中的拖拽、缩放、旋转 (下)——代码实现

简介: Canvas中的拖拽、缩放、旋转 (下)——代码实现

640.gif


上一篇文章介绍了canvas中的拖拽、缩放、旋转中涉及到的数学知识。可以点击下面的链接查看。


canvas中的拖拽、缩放、旋转 (上) —— 数学知识准备


本文将介绍上述demo的实现过程。点击文末的【阅读原文】,体验demo,查看源代码。


代码准备-如何在canvas中画出带旋转角度的元素


canvas中,如果一个元素以中心为旋转原点,进行了一定的旋转,可以直接变化canvas的坐标轴来画出此元素。举个例子,

f2d3a1397066d1f3deccb3f3cb51025f.jpg

我们可以使用变化矩阵来对canvas进行各种变化,这里就不一一介绍了。

从上面的例子可以看出,要画出一个带旋转角度的元素,只需要知道元素的初始位置、大小以及旋转角度即可。


代码整体思路


整个demo的实现思路如下:


  1. 用户开始触摸( touchstart)时,获取用户的触摸对象,是 Sprite的本体?删除按钮?缩放按钮?旋转按钮?并且根据各种情况,对变化参数进行初始化。
  2. 用户移动手指( touchmove)时,根据手指的坐标,更新 stage中的所有元素的位置、大小,记录变化参数。修改对应 sprite的属性值。同时对 canvas进行重绘。
  3. 用户一旦停止触摸( touchend)时,根据变化参数,更新 sprite四个顶点的坐标,同时对变化参数进行重置。


需要注意的是,在 touchmove的过程中,并不需要更新 sprite四个顶点的坐标,只需要记录变化的参数即可。在 touchend过程中,再进行坐标的更新。四个顶点坐标的唯一用处,就是判断用户点击时,落点是否在指定区域内。


代码细节


首先,声明两个类: StageSpriteStage表示整个canvas区域, Sprite表示canvas中的元素。我们可以在 Stage中添加多个 Sprite,删除 Sprite。这两个类的属性如下。

1. class Stage {
2.     constructor(props) {
3. 
4.         this.canvas = props.canvas;
5.         this.ctx = this.canvas.getContext('2d');
6. 
7.         // 用一个数组来保存canvas中的元素。每一个元素都是一个Sprite类的实例。
8.         this.spriteList = []; 
9. 
10.         // 获取canvas在视窗中的位置,以便计算用户touch时,相对与canvas内部的坐标。
11.         const pos = this.canvas.getBoundingClientRect(); 
12.         this.canvasOffsetLeft = pos.left;
13.         this.canvasOffsetTop = pos.top;
14. 
15.         this.dragSpriteTarget = null; // 拖拽的对象
16.         this.scaleSpriteTarget = null; // 缩放的对象
17.         this.rotateSpriteTarget = null; // 旋转的对象
18. 
19.         this.dragStartX = undefined; 
20.         this.dragStartY = undefined;
21.         this.scaleStartX = undefined;
22.         this.scaleStartY = undefined;
23.         this.rotateStartX = undefined;
24.         this.rotateStartY = undefined;
25. 
26.     }
27. }
28. 
29. class Sprite {
30.     constructor(props) {
31. 
32.         // 每一个sprite都有一个唯一的id
33.         this.id = Date.now() + Math.floor(Math.random() * 10);
34. 
35.         this.pos = props.pos; // 在canvas中的位置
36.         this.size = props.size; // sprite的当前大小
37.         this.baseSize = props.size; // sprite的初始化大小
38.         this.minSize = props.minSize; // sprite缩放时允许的最小size
39.         this.maxSize = props.maxSize; // sprite缩放时允许的最大size
40. 
41.         // 中心点坐标
42.         this.center = [
43.             props.pos[0] + props.size[0] / 2, 
44.             props.pos[1] + props.size[1] / 2
45.         ];
46. 
47.         this.delIcon = null;
48.         this.scaleIcon = null;
49.         this.rotateIcon = null;
50. 
51.         // 四个顶点的坐标,顺序为:左上,右上,左下,右下
52.         this.coordinate = this.setCoordinate(this.pos, this.size); 
53. 
54.         this.rotateAngle = 0; // 累计旋转的角度
55.         this.rotateAngleDir = 0; // 每次旋转角度
56. 
57.         this.scalePercent = 1; // 缩放比例
58. 
59.     }
60. }

demo中,点击canvas下方的红色方块时,会实例化一个 sprite,调用 stage.append时,会将实例化的 sprite直接push到 Stage的 spriteList属性内。

1. window.onload = function () {
2. 
3.     const stage = new Stage({
4.         canvas: document.querySelector('canvas')
5.     });
6. 
7.     document.querySelector('.red-box').addEventListener('click', function () {
8.         const randomX = Math.floor(Math.random() * 200);
9.         const randomY = Math.floor(Math.random() * 200);
10.         const sprite = new Sprite({
11.             pos: [randomX, randomY],
12.             size: [120, 60],
13.             minSize: [40, 20],
14.             maxSize: [240, 120]
15.         });
16.         stage.append(sprite);
17.     });
18. }

下面是 Stage的方法:


1. class Stage {
2. 
3.     constructor(props) {}
4. 
5.     // 将sprite添加到stage内
6.     append(sprite) {}
7. 
8.     // 监听事件
9.     initEvent() {}
10. 
11.     // 处理touchstart
12.     handleTouchStart(e) {}
13. 
14.     // 处理touchmove
15.     handleTouchMove(e) {}
16. 
17.     // 处理touchend
18.     handleTouchEnd() {}
19. 
20.     // 初始化sprite的拖拽事件
21.     initDragEvent(sprite, { touchX, touchY }) {}
22. 
23.     // 初始化sprite的缩放事件
24.     initScaleEvent(sprite, { touchX, touchY }) {}
25. 
26.     // 初始化sprite的旋转事件
27.     initRotateEvent(sprite, { touchX, touchY }) {}
28. 
29.     // 通过触摸的坐标重新计算sprite的坐标
30.     reCalSpritePos(sprite, touchX, touchY) {}
31. 
32.     // 通过触摸的【横】坐标重新计算sprite的大小
33.     reCalSpriteSize(sprite, touchX, touchY) {}
34. 
35.     // 重新计算sprite的角度
36.     reCalSpriteRotate(sprite, touchX, touchY) {}
37. 
38.     // 返回当前touch的sprite
39.     getTouchSpriteTarget({ touchX, touchY }) {}
40. 
41.     // 判断是否touch在了sprite中的某一部分上,返回这个sprite
42.     getTouchTargetOfSprite({ touchX, touchY }, part) {}
43. 
44.     // 返回触摸点相对于canvas的坐标
45.     normalizeTouchEvent(e) {}
46. 
47.     // 判断是否在在某个sprite中移动。当前默认所有的sprite都是长方形的。
48.     checkIfTouchIn({ touchX, touchY }, sprite) {}
49. 
50.     // 从场景中删除
51.     remove(sprite) {}
52. 
53.     // 画出stage中的所有sprite
54.     drawSprite() {}
55. 
56.     // 清空画布
57.     clearStage() {}
58. }

Sprite的方法:


class Sprite {
    constructor(props) {}
    // 设置四个顶点的初始化坐标
    setCoordinate(pos, size) {}
    // 根据旋转角度更新sprite的所有部分的顶点坐标
    updateCoordinateByRotate() {}
    // 根据旋转角度更新顶点坐标
    updateItemCoordinateByRotate(target, center, angle){}
    // 根据缩放比例更新顶点坐标
    updateItemCoordinateByScale(sprite, center, scale) {}
    // 根据按钮icon的顶点坐标获取icon中心点坐标
    getIconCenter(iconCoordinate) {}
    // 根据按钮icon的中心点坐标获取icon的顶点坐标
    getIconCoordinateByIconCenter(center) {}
    // 根据缩放比更新顶点坐标
    updateCoordinateByScale() {}
    // 画出该sprite
    draw(ctx) {}
    // 画出该sprite对应的按钮icon
    drawIcon(ctx, icon) {}
    // 对sprite进行初始化
    init() {}
    // 初始化删除按钮,左下角
    initDelIcon() {}
    // 初始化缩放按钮,右上角
    initScaleIcon() {}
    // 初始化旋转按钮,左上角
    initRotateIcon() {}
    // 重置icon的位置与大小
    resetIconPos() {}
    // 根据移动的距离重置sprite所有部分的位置
    resetPos(dirX, dirY) {}
    // 根据触摸点移动的距离计算缩放比,并重置sprite的尺寸
    resetSize(dir) {}
    // 设置sprite的旋转角度
    setRotateAngle(angleDir) {}
}

Stage的方法主要是处理和用户交互的逻辑,得到用户操作的交互参数,然后根据交互参数调用Sprite的方法来进行变化。


具体的代码,可以点击文末的【阅读原文】,通过浏览器的开发者工具查看。


写在后面


本文介绍了文章开头给出的demo的详细实现过程。代码还有很大的优化空间。事实上,工作上的需求并没有要求【旋转】,只需要实现【拖拽】、【缩放】即可。在只实现【拖拽】和【缩放】的情况下,会容易很多,不需要用到四个顶点的坐标以及之前的那些复杂的数学知识。而在自己实现【旋转】的过程中,也学到了很多。符合预期。

相关文章
|
3月前
PPT 动画-多层旋转(圆角三角形)
PPT 动画-多层旋转(圆角三角形)
33 0
|
5月前
|
移动开发 前端开发 JavaScript
HTML5 Canvas平移,放缩,旋转演示
HTML5 Canvas平移,放缩,旋转演示
52 4
|
4月前
|
移动开发 前端开发 HTML5
Canvas画布之100个小球弹射源码
Canvas画布之100个小球弹射源码
|
4月前
|
机器学习/深度学习 前端开发 算法
canvas系列教程03 —— 线的样式、绘制文本、操作图片(图片的渲染、缩放、裁剪、切割、平铺、特效)、变换元素(平移、缩放、旋转)(二)
canvas系列教程03 —— 线的样式、绘制文本、操作图片(图片的渲染、缩放、裁剪、切割、平铺、特效)、变换元素(平移、缩放、旋转)(二)
77 0
|
4月前
|
存储 前端开发 JavaScript
canvas系列教程03 —— 线的样式、绘制文本、操作图片(图片的渲染、缩放、裁剪、切割、平铺、特效)、变换元素(平移、缩放、旋转)(一)
canvas系列教程03 —— 线的样式、绘制文本、操作图片(图片的渲染、缩放、裁剪、切割、平铺、特效)、变换元素(平移、缩放、旋转)(一)
399 0
|
6月前
|
前端开发 JavaScript 搜索推荐
webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)
webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)
147 1
webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)
|
5月前
|
前端开发
canvas图形操作(缩放、旋转、位移)
canvas图形操作(缩放、旋转、位移)
|
前端开发 算法 JavaScript
使用 canvas 拖拽绘制矩形(带撤销)
之前预览网络摄像头的需求又有了下文,要在视频预览之上进行拖拽生成矩形边框,用于后台算法对区域内容进行一些处理。
335 0
|
前端开发 JavaScript
canvas中的拖拽、缩放、旋转 (上) —— 数学知识准备
canvas中的拖拽、缩放、旋转 (上) —— 数学知识准备
886 0
canvas中的拖拽、缩放、旋转 (上) —— 数学知识准备
|
前端开发 数据可视化
canvas绘制折线路径动画
canvas绘制折线路径动画
canvas绘制折线路径动画