资讯专栏INFORMATION COLUMN

canvas(一)移动端拍照调整

Markxu / 2118人阅读

摘要:以前在做移动端拍照调整图片时遇到一些问题,现整理一下也当总结,有不对的地方望不吝赐教。问题移动图片时画面卡顿。解决避免直接改变元素的和,这样会造成页面的重绘,引起卡顿。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94469.html

相关文章

  • 用exfe.js和canvas解决移动 IOS 拍照上传图片翻转问题

    前言 记得16年的时候我初入前端差不多一年,公司做了一个webapp,有上传头像功能,当时这个项目不是我在负责,测试的时候发现苹果用户拍照上传头像会翻转,当时几个前端的同学捯饬了一下午也没解决,结果问题转到我这里,还有半个小时下班;当时也是一脸懵逼,首先想到的是,这怎么判断它是否翻转了呢?安卓没问题啊,有些苹果手机相册里面的图片也没问题啊,js能有这种功能判断吗?上网查资料,果不其然,有!那就是e...

    leap_frog 评论0 收藏0
  • H5拍照、选择图片上传组件核心

    摘要:决定自己写一个移动端图片上传组件。允许多选,加上事件的回调函数。在的回调函数中,我们能通过拿到所选择的文件,但是文件是无法展示在页面上的,通常的做法是使用转为然后展示在页面上。 背景 前段时间项目重构,改成SSR的项目,但之前用的图片选择上传组件不支持SSR(server-side-render)。遂进行了调研,发现很多的工具。但有的太大,有的使用麻烦,有的不满足使用需求。决定自己写一...

    Guakin_Huang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<