canvas(一)移动端拍照调整
以前在做移动端拍照调整图片时遇到一些问题,现整理一下也当总结,有不对的地方望不吝赐教。
问题1:移动图片时画面卡顿。
问题2:旋转图片时夹角问题(这个问题就是知道了不难,不知道就难,这也算是自己新了解到的一个知识点,所以提出来说一下)。
问题3:canvas绘图iphone图片被旋转。
解决1:
避免直接改变元素的left和top,这样会造成页面的重绘,引起卡顿。可以使用translate
解决2:
用向量叉乘,首先定义两个手指的开始点和结束点、调整数据和图片的默认样式,imgPosition
在选择图片后显示根据需要设置
// 手指A fingerA: { startX: 0, startY: 0, endX: 0, endY: 0 }, // 手指B fingerB: { startX: 0, startY: 0, endX: 0, endY: 0 }, // 调整数据 move: { x: 0, y: 0, temX: 0, temY: 0, scale: 1, temScale: 1, allDeg: 0, temDeg: 0 }, // 默认样式 imgPosition: { left: 0, top: 0, width: 0, height: 0 },
由于用的translate和scale,所以本次调整的数据一定要加上上一次的(temX,temY,temScale,temDeg这几个属性是用来临时存放上一次的数据,以便累加)
分别计算开始和结束时两个手指的向量:公式
function Vector (x1, y1, x2, y2) { this.x = x2 - x1 this.y = y2 - y1 } // 开始两个手指的向量 var vector1 = new Vector(this.fingerA.startX, this.fingerA.startY, this.fingerB.startX, this.fingerB.startY) // 结束时两个手指的向量 var vector2 = new Vector(this.fingerA.endX, this.fingerA.endY, this.fingerB.endX, this.fingerB.endY)
计算两个向量的角度:
var cos = calculateVM(vector1, vector2) var angle = Math.acos(cos) * 180 / Math.PI function calculateVM (vector1, vector2) { /* * 向量夹角公式:cosθ=向量a×向量b/|向量a|×|向量b| * 设向量a=(x1,y1),向量b=(x2,y2) * 则 cosθ= 向量a.向量b/|向量a|×|向量b| =(x1x2+y1y2)/[√(x1²+y1²)*√(x2²+y2²)] */ return (vector1.x * vector2.x + vector1.y * vector2.y) / (Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) * Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)) }
然后计算方向:
var direction = calculateVC(vector1, vector2) function calculateVC (vector1, vector2) { // 叉乘公式 return (vector1.x * vector2.y - vector2.x * vector1.y) > 0 ? 1 : -1 }
得到最终的旋转角度
this.move.allDeg = direction * angle + this.move.temDeg
附上部分代码:
// 调整开始 adjustStart: function (e) { let event = e.targetTouches this.fingerA.startX = event[0].pageX this.fingerA.startY = event[0].pageY // 移动 if (event.length === 1) { this.isDrag = true this.isScale = false // 缩放 } else if (event.length === 2) { this.isScale = true this.isDrag = false this.fingerB.startX = event[1].pageX this.fingerB.startY = event[1].pageY } }, // 调整中,移动或缩放 adjustIng: function (e) { let event = e.targetTouches this.fingerA.endX = event[0].pageX this.fingerA.endY = event[0].pageY // 移动 if (this.isDrag) { // 本次移动距离要加上之前移动的距离 this.move.x = this.fingerA.endX - this.fingerA.startX + this.move.temX this.move.y = this.fingerA.endY - this.fingerA.startY + this.move.temY } else if (this.isScale) { // 缩放 this.fingerB.endX = event[1].pageX this.fingerB.endY = event[1].pageY // 两手指间距离 let distanceStart = Math.sqrt(Math.pow(this.fingerA.startX - this.fingerB.startX, 2) + Math.pow(this.fingerA.startY - this.fingerB.startY, 2)) let distanceEnd = Math.sqrt(Math.pow(this.fingerA.endX - this.fingerB.endX, 2) + Math.pow(this.fingerA.endY - this.fingerB.endY, 2)) this.move.scale = distanceEnd / distanceStart * this.move.temScale // 向量叉乘,求出旋转方向及角度 // 开始两个手指的向量 var vector1 = new Vector(this.fingerA.startX, this.fingerA.startY, this.fingerB.startX, this.fingerB.startY) // 结束时两个手指的向量 var vector2 = new Vector(this.fingerA.endX, this.fingerA.endY, this.fingerB.endX, this.fingerB.endY) var cos = calculateVM(vector1, vector2) var angle = Math.acos(cos) * 180 / Math.PI var direction = calculateVC(vector1, vector2) this.move.allDeg = direction * angle + this.move.temDeg } }, // 调整结束 adjustEnd: function (e) { this.move.temX = this.move.x this.move.temY = this.move.y this.move.temScale = this.move.scale this.move.temDeg = this.move.allDeg this.isDrag = false this.isScale = false },
解决3:
现在已得到图片的调整数据,开始进行canvas绘制,这里有两种方式,可以把this.move.scale换算成left和top,也可以直接用canvas的scale,这里我用第二种方式。
正常情况下直接从图库里面选择图片,不会有被默认选旋转的问题,直接画出来就行了
ctxTemp.save() ctxTemp.translate(cx, cy) ctxTemp.rotate(Math.PI / 180 * move.allDeg) ctxTemp.scale(move.scale, move.scale) ctxTemp.translate(-cx, -cy) ctxTemp.drawImage(fileData.image, moveLeft, moveTop, drawWidth, drawHeight) ctxTemp.restore()
注:我的到的调整数据都是基于图片的中心点,所以在旋转缩放canvas时也要设置中心点,即上面代码里面的(cx,cy)
,由(left + w/2, top + h/2)
得到,下面解决图片被默认旋转的情况,思路:先把拍照的默认角度纠正,在按照普通图片处理方式进行,这里用了插件exif-js.js
得到图片的默认信息(默认被旋转的角度),这里以90°为例。
如图:一张图片本来应该像虚线那样,而在实际被旋转成了实线那样
直接将canvas旋转,最后画出来看到的是这样
实际上我们什么都看不到,应为超出了可见范围的左边缘,canvas默认的源点都是坐标系的左上角,注意看图,现在我们用drawImage画图,里面的参数都是left和top互换,w和h互换,要让图再画到可视区域,和没有旋转一样,y方向要变成负,即是-l,drawImage(img, t, -l, h, w)
,解决默认旋转后,在进行接下来的操作,中心点也要相应改变:
ctxTemp.save() ctxTemp.rotate(Math.PI / 180 * 90) // 坐标系变化注意正负 ctxTemp.translate(cy, -cx) ctxTemp.rotate(Math.PI / 180 * move.allDeg) ctxTemp.scale(move.scale, move.scale) ctxTemp.translate(-cy, cx) ctxTemp.drawImage(fileData.image, moveTop, -(moveLeft + drawWidth), drawHeight, drawWidth) ctxTemp.restore()
之前是(cx, cy)
,现在是(cy, -cx)
(90°以外的情况做相应改变)
部分代码:
/* * @imgPosition:图片信息 * @orient: 系统旋转的角度标识 * @move:调整数据 * @fileData: 文件信息 * @clip:剪裁框信息 * @fun:回调 */ function drawImg (imgPosition, orient, move, fileData, clip, fun) { if (fileData.image) { var canvasTemp = document.createElement('canvas') var ctxTemp = canvasTemp.getContext('2d') // canvas宽 var w = 480 // 剪裁框和canvas之比 var ratio = w / clip.clipWidth // canvas高 var h = ratio * clip.clipHeight canvasTemp.height = h canvasTemp.width = w // 中心点 var cx = (imgPosition.left + move.x + imgPosition.width / 2) * ratio var cy = (imgPosition.top + move.y + imgPosition.height / 2) * ratio // 图片相对于canvas的left let moveLeft = (imgPosition.left + move.x) * ratio // 图片相对于canvas的top let moveTop = (imgPosition.top + move.y) * ratio // 图片和canvas的等比宽 let drawWidth = imgPosition.width * ratio // 图片和canvas的等比高 let drawHeight = imgPosition.height * ratio // 90° if (orient === 6) { ctxTemp.save() ctxTemp.rotate(Math.PI / 180 * 90) // 坐标系变化注意正负 ctxTemp.translate(cy, -cx) ctxTemp.rotate(Math.PI / 180 * move.allDeg) ctxTemp.scale(move.scale, move.scale) // 还原坐标系 ctxTemp.translate(-cy, cx) ctxTemp.drawImage(fileData.image, moveTop, -(moveLeft + drawWidth), drawHeight, drawWidth) ctxTemp.restore() } else { ctxTemp.save() ctxTemp.translate(cx, cy) ctxTemp.rotate(Math.PI / 180 * move.allDeg) ctxTemp.scale(move.scale, move.scale) // 还原坐标系 ctxTemp.translate(-cx, -cy) ctxTemp.drawImage(fileData.image, moveLeft, moveTop, drawWidth, drawHeight) ctxTemp.restore() } var base64 = canvasTemp.toDataURL('image/jpeg', 0.8) // console.log(base64) fun(base64) }
写得有错或不好还望大家赐教demo地址