资讯专栏INFORMATION COLUMN

vue-avatar-tailor,vue头像裁剪组件

imccl / 2128人阅读

摘要:实现原理简单,纯技术处理图片,几乎不需要用到相关知识面向人群急于使用头像裁剪组件的同学。裁剪框初始宽高上传图片后,裁剪区将预设为最大裁剪范围。支持矩形裁剪目前九宫仅支持将图片裁剪为正方形,不能裁剪为矩形,该功能将在后续优化。

项目简介

本组件是vue下的头像裁剪组件,可以直接拷贝代码使用,无需安装依赖

使用九宫格进行裁剪,自由选择裁剪区域。

实时预览裁剪后效果。

可以将裁剪好的图片,导出为封装好的file文件,直接上传到服务器。

导出图片链接,可以导出为图片链接,直接使用裁剪后的效果。

实现原理简单,纯CSS技术处理图片,几乎不需要用到canvas相关知识

面向人群

急于使用vue头像裁剪组件的同学。直接下载文件,拷贝代码即可运行。

喜欢看源码,希望了解组件背后原理的同学。
刚接触前端的同学也可以通过本文章养成看源码的习惯。打破对源码的恐惧,相信自己,其实看源码并没有想象中的那么困难

技术难点

头像裁剪效果需要解决这么几个问题

【遮罩镂空效果】进行裁剪时,是先将原图添加一层透明的白色遮罩。并在遮罩中显示一块清晰的区块,作为当前裁剪区。如何制作该裁剪区。

【裁剪框初始宽高】上传图片后,裁剪区将预设为最大裁剪范围。当上传的图片非正方形时,如何设置裁剪区的最大范围。

【实时预览】如何将待裁剪区显示在实时预览区内。

【移动范围约束】移动伸裁剪区时,如何约束裁剪区的位置,让其一直位于原图上。

【拖拽范围扩展位置计算】当对裁剪区进行拉伸时,由于需要保持正方形,拉伸一边,需要将其临边一并拉伸。如何保证拉伸后都不溢出。

实现思路

上传图片

通过临时生成的img标签获取图片的大小信息(getImgSize函数的具体实现请参考源码)

比较原图长和宽,以其长边作为标准进行图片的缩放显示,让整张图显示在待裁剪区域中

以图片缩放后的短边作为选择框的宽,使裁剪框初始显示为最大可选范围

从而解决【裁剪框初始宽高】问题

// 选择图片
fileChange(event) {
  const fileObj = event.target.files[0]
  const reader = new FileReader()
  reader.onload = () => {
    const { selectData, containerBoxData } = this
    this.imgURL = reader.result
    this.getImgSize(this.imgURL).then((result) => { // 获取图片的大小
      if (result.width > result.height) { // 350为外盒子宽高,比较原图长和宽,以其长边作为标准进行图片的缩放显示
        this.scaleRate = 350 / result.width // 获取并记录图片缩放比
        containerBoxData.width = 350
        containerBoxData.height = Math.floor(result.height * this.scaleRate)
        selectData.top = 0
        selectData.left = (350 - containerBoxData.height) / 2 // 裁剪选择框居中显示
        selectData.width = containerBoxData.height // 以图片缩放后的短边作为选择框的宽,使其显示为最大可选范围
      } else {
        this.scaleRate = 350 / result.height
        containerBoxData.height = 350
        containerBoxData.width = Math.floor(result.width * this.scaleRate)
        selectData.left = 0
        selectData.top = (350 - containerBoxData.width) / 2
        selectData.width = containerBoxData.width
      }
      this.setPreview()
    })
  }
  reader.readAsDataURL(fileObj)
},

遮罩镂空的效果实现思路如下

共通过三个元素重叠显示,原图img标签放在最底下,遮罩层.img-mask放在中间,裁剪区域.select-box放在最外层

.img-mask是遮住了整个img标签的,只是裁剪区域.select-box显示的内容刚好与原图上的一致。实现看起来遮罩被镂空的效果

其实遮罩并没有被镂空,只是遮罩上又多了一层图案,刚刚与原位置相同

裁剪区域.select-box,显示的内容和位置,通过background相关属性,background-position 和 background-size在实现

即选取图片的特定区域作为裁剪区域.select-box的背景

  
  

渲染预览效果

利用canvas的drawImage函数,将候选区域显示在预览区

drawImage函数可以设置图形采取的范围和位置

并设置采取获得的图形,显示在canvas的那个地方,以多大的size显示

从而实现采取区,于预览区缩放的形式显示

实现【实时预览】效果

// 设置预览图
setPreview() {
  const { selectData, scaleRate } = this
  const $canvas = this.$refs.$canvas.getContext("2d")
  $canvas.clearRect(0, 0, 190, 190) // 清除canvas中的内容
  $canvas.drawImage( // 将原图中,选定区域的图案通过canvas渲染出来
    this.$refs.$img, // 图形元素
    Math.floor(selectData.left / scaleRate), // 截取原图中的那个位置作为起始点,X轴方向上
    Math.floor(selectData.top / scaleRate), // 截取原图中的那个位置作为起始点,Y轴方向上
    selectData.width / scaleRate, // 截取原图中多大的范围,宽度
    selectData.width / scaleRate, // 截取原图中多大的范围,高度
    0, // 显示在canvas中的X坐标
    0, // 显示在canvas中的Y坐标
    190, // 将内容伸缩为多大的宽度显示
    190, // 将内容伸缩为多大的高度显示
  )
},

拉伸操作的监听

在裁剪区域中,使用ul,li标签充当各个操作点,及形成九宫格中的虚线功能

在每个操作点中,监听mousedown事件,记录即将移动的方向,表示接下来的mousemove事件中,是进行那个方向上的拉伸

在created钩子中,监听全局 mouseup 和 mousemove 事件。因为鼠标的离开和利用不一样在裁剪区域中,在其他地方触发也应该同样执行相关操作

在 mousemove 事件中区裁剪区移动操作「move」,裁剪区拉伸操作「stretch」。执行不同的函数

数据设置完成后,重新渲染预览区

实现【拖拽范围扩展位置计算】及【拖拽范围扩展约束】功能

onMouseMove(event) {
  const { selectData, containerBoxData } = this
  const { x, y } = selectData.originPoint
  const moveX = event.clientX - x // X轴移动的距离
  const moveY = event.clientY - y // Y轴移动的距离
  if (selectData.action === "move") { // 移动选择框
    this.doMove(selectData, containerBoxData, moveX, moveY)
  } else if (selectData.action === "stretch") { // 拉伸选择框
    this.doStretch(selectData, containerBoxData, moveX, moveY)
  } else {
    return
  }

  selectData.originPoint = {
    x: event.clientX > 0 ? event.clientX : 0,
    y: event.clientY > 0 ? event.clientY : 0,
  }

  this.setPreview()
},

裁剪区移动

比较移动后上下左右各个边与原图可裁剪区域的位置关系

若超出范围,则设置为边界值

实现【移动范围约束】功能

// 鼠标移动
doMove(selectData, containerBoxData, moveX, moveY) {
  selectData.top += moveY
  selectData.left += moveX
  if (selectData.top < 0) {
    selectData.top = 0
  }
  if (selectData.left < 0) {
    selectData.left = 0
  }
  if (selectData.top + selectData.width > containerBoxData.height) {
    selectData.top = containerBoxData.height - selectData.width
  }
  if (selectData.left + selectData.width > containerBoxData.width) {
    selectData.left = containerBoxData.width - selectData.width
  }
},

裁剪区拉伸

设置各个方位具体的拉伸操作

调用对应函数,先进行一次拉伸操作

拉伸完成后,比较上下左右,宽高的溢出情况,获得最大的溢出值

以最大溢出值进行反向操作,确保裁剪区一直在可选范围内

 // 选择框拉伸
doStretch(selectData, containerBoxData, moveX, moveY) {
  const { minWidth } = this

  // 比较上下左右,宽高的溢出情况,返回最大的溢出值
  function getOverflowLength() {
    const overflowLeft = selectData.left < 0 ? -selectData.left : 0
    const overflowTop = selectData.top < 0 ? -selectData.top : 0
    const overflowRight = selectData.left + selectData.width > containerBoxData.width ? selectData.left + selectData.width - containerBoxData.width : 0
    const overflowBottom = selectData.top + selectData.width > containerBoxData.height ? selectData.top + selectData.width - containerBoxData.height : 0
    const overflowWidth = selectData.width < minWidth ? minWidth - selectData.width : 0
    return Math.max(overflowLeft, overflowTop, overflowRight, overflowBottom, overflowWidth)
  }

  // 向左拉伸
  function doStretchLeft(action) {
    let space = moveX
    space = action === "preDo" ? space : -space
    selectData.top += space / 2
    selectData.left += space
    selectData.width -= space
  }

  function doStretchRight(action) {
    let space = moveX
    space = action === "preDo" ? space : -space
    selectData.top -= space / 2
    selectData.width += space
  }

  function doStretchTop(action) {
    let space = moveY
    space = action === "preDo" ? space : -space
    selectData.top += space
    selectData.left += space / 2
    selectData.width -= space
  }

  function doStretchBottom(action) {
    let space = moveY
    space = action === "preDo" ? space : -space
    selectData.left -= space / 2
    selectData.width += space
  }

  function doStretchTopLeft(action) {
    let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : moveY
    space = action === "preDo" ? space : -space
    selectData.top += space
    selectData.left += space
    selectData.width -= space
  }

  function doStretchTopRight(action) {
    let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : -moveY
    space = action === "preDo" ? space : -space
    selectData.top -= space
    selectData.width += space
  }

  function doStretchBottomLeft(action) {
    let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : -moveY
    space = action === "preDo" ? space : -space
    selectData.left += space
    selectData.width -= space
  }

  function doStretchBottomRight(action) {
    let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : moveY
    space = action === "preDo" ? space : -space
    selectData.width += space
  }

  const doStretchFun = {
    doStretchLeft,
    doStretchRight,
    doStretchTop,
    doStretchBottom,
    doStretchTopLeft,
    doStretchTopRight,
    doStretchBottomLeft,
    doStretchBottomRight,
  }[`doStretch${this.getWord(this.getCamelCase(selectData.direction))}`]

  // 进行拉伸操作
  doStretchFun("preDo")
  let overflowLength = getOverflowLength() // 拉伸完成后,获取最大溢出值
  if (overflowLength > 0) { // 以最大溢出值进行反向操作,确保裁剪区一直在可选范围内
    doStretchFun("reset")
  }
},

后续优化

【代码优化】可以看到上面的代码,有部分冗余,略显繁琐。后续将简化实现逻辑。

【支持矩形裁剪】目前九宫仅支持将图片裁剪为正方形,不能裁剪为矩形,该功能将在后续优化。

【原图的缩放】目前上传的裁剪目标图并不支持缩放,上传大图时,裁剪显得不那么灵活,这也将在后续优化

项目源码及示例 最后送上一张示例中使用的乔巴表情图

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

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

相关文章

  • 编写一个头像裁剪组件(一)

    摘要:需求是编写一个头像剪裁工具再封装成为一个组件,然后根据功能开始逐步编写代码先是上传图片预览图片编辑图片。 需求是编写一个头像剪裁工具再封装成为一个组件,然后根据功能开始逐步编写代码:先是上传图片 => 预览图片 => 编辑图片。 刚开始没有去考虑兼容性的问题,直接编写upload部分的代码,参考了很多代码还有HTML5 FILE API之后,发现很少有React编写的这样的代码,因为想...

    whatsns 评论0 收藏0
  • 腾讯 AlloyTeam 移动 Web 裁剪组件 AlloyCrop 正式开源

    摘要:兼容性如何支持以及的设备的浏览器便可运行不一一列举一共不到行为什么体积这么小腾讯手内大量的都会去不断地从各个维度进行性能优化。腾讯内部有哪些项目在用目前主要是兴趣部落群等业务在用,刚刚开源出来,只要有裁剪图片的地方都会用到。 传送门 Github地址:https://github.com/AlloyTeam/AlloyFinger/tree/master/alloy_crop 在线De...

    yexiaobai 评论0 收藏0
  • vue自定义指令clickoutside扩展--多个元素的并集作为inside

    摘要:指令中自定义的指令之一,顾名思义,就是当鼠标点击了指令所绑定元素的外部时,就会触发绑定方法。在鼠标放开触发事件处理时,通过获取到他们的对象。使用示例原来的使用方式不受影响,只是添加了多个元素并集作为的功能。指令中的参数学习 都是个人理解,如果发现错误,恳请大家批评指正,谢谢。还有我说的会比较啰嗦,因为是以自身菜鸡水平的视角来记录学习理解的过程,见谅。 1.前言 产品使用vue+elem...

    ivan_qhz 评论0 收藏0
  • vue2+element 管理后台 集成解决方案 没有没做的,只要想不到的!

    摘要:目前的技术栈主要的采用由于是个人项目,所以数据请求都是用了代替。后续会出一系列的教程配套文章,如如何从零构建后台项目框架,如何做完整的用户系统如权限验证,二次登录等,如何二次开发组件如富文本,如何整合七牛等等文章,各种后台开发经验等等。 完整项目地址:vue-element-admin系类文章一:手摸手,带你用vue撸后台 系列一(基础篇)系类文章二:手摸手,带你用vue撸后台 系列二...

    sanyang 评论0 收藏0

发表评论

0条评论

imccl

|高级讲师

TA的文章

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