以前在做移动端拍照调整图片时遇到一些问题,现整理一下也当总结,有不对的地方望不吝赐教。
问题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地址