内容简介:本文首发于公众号:符合预期的CoyPandemo体验地址及代码在这里:
写在前面
本文首发于公众号:符合预期的CoyPan
demo体验地址及代码在这里: 请用手机或浏览器模拟手机访问
上一篇文章介绍了canvas中的拖拽、缩放、旋转中涉及到的数学知识。可以点击下面的链接查看。
canvas中的拖拽、缩放、旋转 (上) —— 数学知识准备。
代码准备 - 如何在canvas中画出一个带旋转角度的元素
在 canvas
中,如果一个元素带有一个旋转角度,可以直接变化 canvas
的坐标轴来画出此元素。举个例子,
代码整体思路
整个demo的实现思路如下:
- 用户开始触摸(
touchstart
)时,获取用户的触摸对象,是Sprite
的本体?删除按钮?缩放按钮?旋转按钮?并且根据各种情况,对变化参数进行初始化。 - 用户移动手指(
touchmove
)时,根据手指的坐标,更新stage
中的所有元素的位置、大小,记录变化参数。修改对应sprite
的属性值。同时对canvas
进行重绘。 - 用户一旦停止触摸(
touchend
)时,根据变化参数,更新sprite
的坐标,同时对变化参数进行重置。
需要注意的是,在 touchmove
的过程中,并不需要更新 sprite
的坐标,只需要记录变化的参数即可。在 touchend
过程中,再进行坐标的更新。坐标的唯一用处,就是判断用户点击时,落点是否在指定区域内。
代码细节
首先,声明两个类: Stage
和 Sprite
。 Stage
表示整个canvas区域, Sprite
表示canvas中的元素。我们可以在 Stage
中添加多个 Sprite
,删除 Sprite
。这两个类的属性如下。
class Stage { constructor(props) { this.canvas = props.canvas; this.ctx = this.canvas.getContext('2d'); // 用一个数组来保存canvas中的元素。每一个元素都是一个Sprite类的实例。 this.spriteList = []; // 获取canvas在视窗中的位置,以便计算用户touch时,相对与canvas内部的坐标。 const pos = this.canvas.getBoundingClientRect(); this.canvasOffsetLeft = pos.left; this.canvasOffsetTop = pos.top; this.dragSpriteTarget = null; // 拖拽的对象 this.scaleSpriteTarget = null; // 缩放的对象 this.rotateSpriteTarget = null; // 旋转的对象 this.dragStartX = undefined; this.dragStartY = undefined; this.scaleStartX = undefined; this.scaleStartY = undefined; this.rotateStartX = undefined; this.rotateStartY = undefined; } } class Sprite { constructor(props) { // 每一个sprite都有一个唯一的id this.id = Date.now() + Math.floor(Math.random() * 10); this.pos = props.pos; // 在canvas中的位置 this.size = props.size; // sprite的当前大小 this.baseSize = props.size; // sprite的初始化大小 this.minSize = props.minSize; // sprite缩放时允许的最小size this.maxSize = props.maxSize; // sprite缩放时允许的最大size // 中心点坐标 this.center = [ props.pos[0] + props.size[0] / 2, props.pos[1] + props.size[1] / 2 ]; this.delIcon = null; this.scaleIcon = null; this.rotateIcon = null; // 四个顶点的坐标,顺序为:左上,右上,左下,右下 this.coordinate = this.setCoordinate(this.pos, this.size); this.rotateAngle = 0; // 累计旋转的角度 this.rotateAngleDir = 0; // 每次旋转角度 this.scalePercent = 1; // 缩放比例 } }
demo中,点击canvas下方的红色方块时,会实例化一个 sprite
,调用 stage.append
时,会将实例化的 sprite
直接push到 Stage
的 spriteList
属性内。
window.onload = function () { const stage = new Stage({ canvas: document.querySelector('canvas') }); document.querySelector('.red-box').addEventListener('click', function () { const randomX = Math.floor(Math.random() * 200); const randomY = Math.floor(Math.random() * 200); const sprite = new Sprite({ pos: [randomX, randomY], size: [120, 60], minSize: [40, 20], maxSize: [240, 120] }); stage.append(sprite); }); }
下面是 Stage
的方法:
class Stage { constructor(props) {} // 将sprite添加到stage内 append(sprite) {} // 监听事件 initEvent() {} // 处理touchstart handleTouchStart(e) {} // 处理touchmove handleTouchMove(e) {} // 处理touchend handleTouchEnd() {} // 初始化sprite的拖拽事件 initDragEvent(sprite, { touchX, touchY }) {} // 初始化sprite的缩放事件 initScaleEvent(sprite, { touchX, touchY }) {} // 初始化sprite的旋转事件 initRotateEvent(sprite, { touchX, touchY }) {} // 通过触摸的坐标重新计算sprite的坐标 reCalSpritePos(sprite, touchX, touchY) {} // 通过触摸的【横】坐标重新计算sprite的大小 reCalSpriteSize(sprite, touchX, touchY) {} // 重新计算sprite的角度 reCalSpriteRotate(sprite, touchX, touchY) {} // 返回当前touch的sprite getTouchSpriteTarget({ touchX, touchY }) {} // 判断是否touch在了sprite中的某一部分上,返回这个sprite getTouchTargetOfSprite({ touchX, touchY }, part) {} // 返回触摸点相对于canvas的坐标 normalizeTouchEvent(e) {} // 判断是否在在某个sprite中移动。当前默认所有的sprite都是长方形的。 checkIfTouchIn({ touchX, touchY }, sprite) {} // 从场景中删除 remove(sprite) {} // 画出stage中的所有sprite drawSprite() {} // 清空画布 clearStage() {} }
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
的方法来进行变化。
代码在这里: https://coypan.info/demo/canvas-drag-scale-rotate.html
写在后面
本文介绍了文章开头给出的demo的详细实现过程。代码还有很大的优化空间。事实上,工作上的需求并没有要求【旋转】,只需要实现【拖拽】、【缩放】即可。在只实现【拖拽】和【缩放】的情况下,会容易很多,不需要用到四个顶点的坐标以及之前的那些复杂的数学知识。而在自己实现【旋转】的过程中,也学到了很多。符合预期。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- canvas中的拖拽、缩放、旋转 (上) —— 数学知识准备
- 使用RxJava实现ImageView的拖动、旋转和缩放
- Three.js三维模型几何体旋转、缩放和平移
- [译] 为什么要做特征缩放,怎么做特征缩放,什么时候做特征缩放?特征缩放三连了解一下
- golang使用nfnt缩放图片
- 特征工程之特征缩放 & 特征编码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
人件(原书第3版)
[美] Tom DeMarco、[美] Timothy Lister / 肖然、张逸、滕云 / 机械工业出版社 / 2014-8 / 69.00元
在软件管理领域,很少有著作能够与本书媲美。作为经久不衰的畅销书,本书深刻地洞察到软件开发的最大问题不在于技术,而在于人。人的因素并不容易解决,一旦解决了,你将更有可能获得成功。 本书是软件管理领域的传奇经典,被誉为“对美国软件业影响最大的一本书”。全书从管理人力资源、创建健康的办公环境、雇用并留用正确的人、高效团队形成、改造企业文化和快乐工作等多个角度阐释了如何思考和管理软件开发的最大问题—......一起来看看 《人件(原书第3版)》 这本书的介绍吧!
UNIX 时间戳转换
UNIX 时间戳转换
RGB CMYK 转换工具
RGB CMYK 互转工具