项目简介

  • 本组件是vue下的头像裁剪组件,可以直接拷贝代码使用,无需安装依赖
  • 使用九宫格进行裁剪,自由选择裁剪区域。
  • 实时预览裁剪后效果。
  • 可以将裁剪好的图片,导出为封装好的file文件,直接上传到服务器。
  • 导出图片链接,可以导出为图片链接,直接使用裁剪后的效果。
  • 实现原理简单,纯CSS技术处理图片,几乎不需要用到canvas相关知识

面向人群

  • 急于使用vue头像裁剪组件的同学。直接下载文件,拷贝代码即可运行。
  • 喜欢看源码,希望了解组件背后原理的同学。
    刚接触前端的同学也可以通过本文章养成看源码的习惯。打破对源码的恐惧,相信自己,其实看源码并没有想象中的那么困难

技术难点

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

  • 【遮罩镂空效果】进行裁剪时,是先将原图添加一层透明的白色遮罩。并在遮罩中显示一块清晰的区块,作为当前裁剪区。如何制作该裁剪区。
  • 【裁剪框初始宽高】上传图片后,裁剪区将预设为最大裁剪范围。当上传的图片非正方形时,如何设置裁剪区的最大范围。
  • 【实时预览】如何将待裁剪区显示在实时预览区内。
  • 【移动范围约束】移动伸裁剪区时,如何约束裁剪区的位置,让其一直位于原图上。
  • 【拖拽范围扩展位置计算】当对裁剪区进行拉伸时,由于需要保持正方形,拉伸一边,需要将其临边一并拉伸。如何保证拉伸后都不溢出。

实现思路

  • 上传图片

    • 通过临时生成的img标签获取图片的大小信息(getImgSize函数的具体实现请参考源码)
    • 比较原图长和宽,以其长边作为标准进行图片的缩放显示,让整张图显示在待裁剪区域中
    • 以图片缩放后的短边作为选择框的宽,使裁剪框初始显示为最大可选范围
    • 从而解决【裁剪框初始宽高】问题
    // 选择图片
    fileChange(event) {const fileObj = event.target.files[0]const reader = new FileReader()reader.onload = () => {const { selectData, containerBoxData } = thisthis.imgURL = reader.resultthis.getImgSize(this.imgURL).then((result) => { // 获取图片的大小if (result.width > result.height) { // 350为外盒子宽高,比较原图长和宽,以其长边作为标准进行图片的缩放显示this.scaleRate = 350 / result.width // 获取并记录图片缩放比containerBoxData.width = 350containerBoxData.height = Math.floor(result.height * this.scaleRate)selectData.top = 0selectData.left = (350 - containerBoxData.height) / 2 // 裁剪选择框居中显示selectData.width = containerBoxData.height // 以图片缩放后的短边作为选择框的宽,使其显示为最大可选范围} else {this.scaleRate = 350 / result.heightcontainerBoxData.height = 350containerBoxData.width = Math.floor(result.width * this.scaleRate)selectData.left = 0selectData.top = (350 - containerBoxData.width) / 2selectData.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的背景
      <img ref="$img" :src="imgURL"/><div class="img-mask"></div><div class="select-box" v-if="imgURL"@mousedown="onMouseDown($event, 'move')" :style="{top:`${selectData.top}px`,left:`${selectData.left}px`,width:`${selectData.width}px`,height:`${selectData.width}px`,'background-image':`url(${imgURL})`,'background-position':`${-selectData.left}px ${-selectData.top}px`,'background-size':`${containerBoxData.width}px ${containerBoxData.height}px`,}"></div>
  • 渲染预览效果

    • 利用canvas的drawImage函数,将候选区域显示在预览区
    • drawImage函数可以设置图形采取的范围和位置
    • 并设置采取获得的图形,显示在canvas的那个地方,以多大的size显示
    • 从而实现采取区,于预览区缩放的形式显示
    • 实现【实时预览】效果
    // 设置预览图
    setPreview() {const { selectData, scaleRate } = thisconst $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」。执行不同的函数
    • 数据设置完成后,重新渲染预览区
    • 实现【拖拽范围扩展位置计算】及【拖拽范围扩展约束】功能
    <div class="select-box" v-if="imgURL" @mousedown="onMouseDown($event, 'move')"><ul class="x-line"><li @mousedown.stop="onMouseDown($event, 'stretch', 'top')"></li><li></li><li></li><li @mousedown.stop="onMouseDown($event, 'stretch', 'bottom')"></li></ul><ul class="y-line"><li @mousedown.stop="onMouseDown($event, 'stretch', 'left')"></li><li></li><li></li><li @mousedown.stop="onMouseDown($event, 'stretch', 'right')"></li></ul><ul class="point"><li @mousedown.stop="onMouseDown($event, 'stretch', 'top-left')"></li><li @mousedown.stop="onMouseDown($event, 'stretch', 'top-right')"></li><li @mousedown.stop="onMouseDown($event, 'stretch', 'bottom-left')"></li><li @mousedown.stop="onMouseDown($event, 'stretch', 'bottom-right')"></li></ul><div class="cross"></div>
    </div>
    onMouseMove(event) {const { selectData, containerBoxData } = thisconst { x, y } = selectData.originPointconst 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 += moveYselectData.left += moveXif (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 : 0const overflowTop = selectData.top < 0 ? -selectData.top : 0const overflowRight = selectData.left + selectData.width > containerBoxData.width ? selectData.left + selectData.width - containerBoxData.width : 0const overflowBottom = selectData.top + selectData.width > containerBoxData.height ? selectData.top + selectData.width - containerBoxData.height : 0const overflowWidth = selectData.width < minWidth ? minWidth - selectData.width : 0return Math.max(overflowLeft, overflowTop, overflowRight, overflowBottom, overflowWidth)}// 向左拉伸function doStretchLeft(action) {let space = moveXspace = action === 'preDo' ? space : -spaceselectData.top += space / 2selectData.left += spaceselectData.width -= space}function doStretchRight(action) {let space = moveXspace = action === 'preDo' ? space : -spaceselectData.top -= space / 2selectData.width += space}function doStretchTop(action) {let space = moveYspace = action === 'preDo' ? space : -spaceselectData.top += spaceselectData.left += space / 2selectData.width -= space}function doStretchBottom(action) {let space = moveYspace = action === 'preDo' ? space : -spaceselectData.left -= space / 2selectData.width += space}function doStretchTopLeft(action) {let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : moveYspace = action === 'preDo' ? space : -spaceselectData.top += spaceselectData.left += spaceselectData.width -= space}function doStretchTopRight(action) {let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : -moveYspace = action === 'preDo' ? space : -spaceselectData.top -= spaceselectData.width += space}function doStretchBottomLeft(action) {let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : -moveYspace = action === 'preDo' ? space : -spaceselectData.left += spaceselectData.width -= space}function doStretchBottomRight(action) {let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : moveYspace = action === 'preDo' ? space : -spaceselectData.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')}
    },

后续优化

  • 【代码优化】可以看到上面的代码,有部分冗余,略显繁琐。后续将简化实现逻辑。
  • 【支持矩形裁剪】目前九宫仅支持将图片裁剪为正方形,不能裁剪为矩形,该功能将在后续优化。
  • 【原图的缩放】目前上传的裁剪目标图并不支持缩放,上传大图时,裁剪显得不那么灵活,这也将在后续优化

项目源码及示例

最后送上一张示例中使用的乔巴表情图

vue-avatar-tailor,vue头像裁剪组件相关推荐

  1. 头像裁剪框html css,GitHub - saintic/layui-cropper-avatar: Layui头像裁剪组件

    layui-cropper-avatar Layui头像裁剪组件 说明 这是一款适用于Layui的[微改整合]第三方组件,基于cropper 3.1.3,实现点击一个按钮弹出裁剪框,完成选择.裁剪图片 ...

  2. 用react模仿知乎的用户头像裁剪组件

    用react模仿知乎的用户头像裁剪组件 前段时间刷知乎的时候,更换换了下头像,突然发现知乎头像编辑器很简洁酷炫,于是乎就用react尝试模仿做一个组件. 知乎上的这个是用canvas处理的,但是用di ...

  3. Vue 如何快速实现头像裁剪?方法比你想象的简单

    高效实现需求的方法,就是避免重复造轮子.图片裁剪的插件也不少,这里我选择 vue-cropper,原因是功能强大.简单易上手.话不多说,上效果图: 效果图 安装 npm install vue-cro ...

  4. 【Vue】移动端图片裁剪组件--vue-imgcut

    移动端图片裁剪组件–vue-imgcut 安装 npm install vue-imgcut --save GitHub地址 使用说明 template模板中 callback :回调函数 img 是 ...

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

    需求是编写一个头像剪裁工具再封装成为一个组件,然后根据功能开始逐步编写代码:先是上传图片 => 预览图片 => 编辑图片. 刚开始没有去考虑兼容性的问题,直接编写upload部分的代码,参 ...

  6. vue 移动端头像裁剪_vue头像上传裁剪组件_一个漂亮的Vue组件,用于图像裁剪和上传...

    vue头像上传裁剪组件 vue-image-crop-upload (vue-image-crop-upload) A beautiful vue component for image crop a ...

  7. VUE图片裁剪组件,基于VueCropper

    VUE图片裁剪组件,基于VueCropper 搬砖的同志麻烦点个赞支持下 效果图 第一步安装vue-cropper插件 npm install vue-cropper 第二步创建组件,把下面的代码复制 ...

  8. vue项目中实现头像上传的功能型组件

    @vue项目中实现头像上传的功能型组件 功能需求 实现个人中心的头像上传功能: 1.用户未上传过头像,展示设定好的默认头像(区分男女) 2.支持格式png.jpg.jpeg 3.图片大小:小于等于2M ...

  9. vue项目中-上传图片头像并裁剪成任意大小的实现

    vue项目中-上传图片头像并裁剪成任意大小的实现 先看效果图: 放大缩小-翻转-查看都有的哦! 直接上代码 <el-dialog title="图片剪裁" :visible. ...

最新文章

  1. 设计模式之桥接模式(Bridge)摘录
  2. 如何在用户登录时SAP时自动执行Tcode或者其他一些东西
  3. java i 什么时候变_Java中i++与++i的区别(效率分析)
  4. (9) ab测试工具安装与使用
  5. Jmeter 老司机带你一小时学会Jmeter
  6. java用DFA实现脏词过滤以及用FileAlterationListenerAdaptor实现对资源文件修改的动态监听
  7. 管理科学与运筹学(MS/OR)国际权威期刊
  8. 哄女朋友玩的c语言编程,哄女朋友开心的小套路 逗女朋友开心的话套路
  9. java拆箱 装箱 一篇文章就够了
  10. 第六章:详细设计。盒图、问题分析图即PAD图、过程设计语言PDL伪码
  11. 即构推出视频见证系统方案,全面支持泛金融实时双录
  12. Redis源码精炼版
  13. Windows10环境下设置播放器默认软件打开方式
  14. 上市即亏损?谈谈爱奇艺和拼多多上市后的艰难人生
  15. 51Openlab上Mininet快速入门
  16. mysql 多条件like语句、正则regexp
  17. 转义符——反斜杠(\)
  18. ascii码,gbk编码,unicodo码,utf-8码
  19. 二叉树节点和度的关系及特点
  20. 第22课 Altium Designer20(AD20)+VESC6.4实战教程:走线和扇孔2(北冥有鱼)

热门文章

  1. 七夕表白如何万无一失!用我这个方法保证你一定成功!
  2. Native 内存wrap
  3. 趋势科技PC-cillin2015,你来公测我发奖!
  4. 【2019暑假集训】07.05比赛总结
  5. Google Earth打开就关闭了!
  6. HDU1177做题笔记
  7. 程序员之路-学习经验总结分享
  8. CAD入门学习笔记111
  9. 一个人会不会成功,只看是否拎得清…
  10. windows下的gjf转到Linux,通过命令行将 MP4 转换成 gif