最近在做项目中需要裁剪图片上某一处位置的头像,这边就记录下我的实现的操作

演示效果:

 直接上代码:

<template><div v-if="croppingUrl" class="model"><h5 style="margin: 0 0 1rem;">{{title ? title : '裁剪图片'}}</h5><div style="display: flex;"><div class="cutting" :style="{width: croppingImageSize.width + 'px', height: croppingImageSize.height + 'px'}"><img :src="croppingUrl" alt="..." :style="{left: croppingImageAttr.x + 'px', top: croppingImageAttr.y + 'px', width: croppingImageAttr.width + 'px', height: croppingImageAttr.height + 'px'}" /><div id="cropping-mask"></div><div class="region" :style="{'clip-path':`polygon(${croppingRegionAttr.x}px ${croppingRegionAttr.y}px,${croppingRegionAttr.x + croppingRegionAttr.width}px ${croppingRegionAttr.y}px,${croppingRegionAttr.x + croppingRegionAttr.width}px ${croppingRegionAttr.y + croppingRegionAttr.height}px,${croppingRegionAttr.x}px ${croppingRegionAttr.y + croppingRegionAttr.height}px)`}"><img :src="croppingUrl" alt="..." :style="{left: croppingImageAttr.x + 'px', top: croppingImageAttr.y + 'px', width: croppingImageAttr.width + 'px', height: croppingImageAttr.height + 'px'}" /><div id="cropping-region" :style="{width: croppingRegionAttr.width + 'px', height: croppingRegionAttr.height + 'px', left: croppingRegionAttr.x + 'px', top: croppingRegionAttr.y + 'px'}"></div></div></div><div style="margin-left: 1rem;"><div style="margin-bottom: 1rem;">预览</div><div style="background-repeat: no-repeat;" :style="{'background-image': `url(${croppingUrl})`,width: `${croppingRegionAttr.width}px`,height: `${croppingRegionAttr.height}px`,'background-size': `${croppingImageAttr.width}px ${croppingImageAttr.height}px`,'background-position': `-${croppingRegionAttr.x}px -${croppingRegionAttr.y}px`}"></div></div></div><button @click="clickSave" style="margin-top: 10px;">保存</button></div>
</template><script lang="ts">
import {defineComponent, onUnmounted, reactive, toRefs, watch} from 'vue';export default defineComponent({props: {/** 弹框标题 */title: {type: String,default: ""},/** 需要裁剪的图片文件 */file: {type: File|Object,default: null,}},setup(props, { emit }) {const state = reactive({// 裁剪图片弹框标题title: props.title,// 当前是否允许拖拽 - 裁剪区域isCroppingRegionDrag: false,// 当前是否允许拖拽 - 裁剪图片isCroppingImgDrag: false,// 裁剪区域可移动的范围croppingImageSize: {width: 400,height: 400 / 1.59,},// 裁剪区域的属性croppingRegionAttr: {width: 145,height: 180,x: 230,y: 25,},// 记录上一次的位置recordPrevPosition: null as any,// 记录裁剪的图片文件recordCroppingFile: null as any,// 裁剪图片的urlcroppingUrl: "" as any,// 被裁剪图片的属性croppingImageAttr: {width: 0,height: 0,x: 0,y: 0,},// 裁剪后的图片 - base64croppingAfterImage: "",});watch(() => props.file, async newVal => {state.recordCroppingFile = newVal;if(newVal) {state.croppingUrl = await getBase64ByFile(state.recordCroppingFile);nextTick(() => init());}});onUnmounted(() => {destroy();});/** 初始化操作 */const init = () => {document.getElementById("cropping-region")?.addEventListener("mousedown", croppingRegionOnmousedown);document.getElementById("cropping-region")?.addEventListener("mousemove", croppingRegionOnmousemove);document.getElementById("cropping-mask")?.addEventListener("mousedown", croppingImgOnmousedown);document.getElementById("cropping-mask")?.addEventListener("mousemove", croppingImgOnmousemove);document.addEventListener("mouseup", onmouseup);}/** 结束的操作 */const destroy = () => {document.getElementById("cropping-region")?.removeEventListener("mousedown", croppingRegionOnmousedown);document.getElementById("cropping-region")?.removeEventListener("mousemove", croppingRegionOnmousemove);document.getElementById("cropping-mask")?.removeEventListener("mousedown", croppingImgOnmousedown);document.getElementById("cropping-mask")?.removeEventListener("mousemove", croppingImgOnmousemove);document.removeEventListener("mouseup", onmouseup);}/** 监听鼠标按下事件 - 裁剪区域 */const croppingRegionOnmousedown = event => {event.preventDefault();state.isCroppingRegionDrag = true;}/** 监听鼠标移动事件 - 裁剪区域 */const croppingRegionOnmousemove = event => {if(state.isCroppingRegionDrag && state.croppingUrl) {const { clientX, clientY } = event;let poorX = 0;let poorY = 0;// 获取移动的差if(state.recordPrevPosition) {poorX = clientX - state.recordPrevPosition.x;poorY = clientY - state.recordPrevPosition.y;}// 记录移动的位置state.recordPrevPosition = { x: clientX, y: clientY };// 判断是否允许移动const { x, y, width, height } = state.croppingRegionAttr;// 设置移动的值state.croppingRegionAttr.x = x + poorX < 0 ? 0 : x + poorX + width > state.croppingImageSize.width ? state.croppingImageSize.width - width : x + poorX;state.croppingRegionAttr.y = y + poorY < 0 ? 0 : y + poorY + height > state.croppingImageSize.height ? state.croppingImageSize.height - height : y + poorY;}}/** 监听鼠标抬起事件 */const onmouseup = () => {state.isCroppingRegionDrag = false;state.isCroppingImgDrag = false;state.recordPrevPosition = null;}/** 监听鼠标按下事件 - 裁剪图片 */const croppingImgOnmousedown = event => {event.preventDefault();state.isCroppingImgDrag = true;}/** 监听鼠标移动事件 - 裁剪图片 */const croppingImgOnmousemove = event => {if(state.isCroppingImgDrag && state.croppingUrl) {const { clientX, clientY } = event;let poorX = 0;let poorY = 0;// 获取移动的差if(state.recordPrevPosition) {poorX = clientX - state.recordPrevPosition.x;poorY = clientY - state.recordPrevPosition.y;}// 记录移动的位置state.recordPrevPosition = { x: clientX, y: clientY };// 判断是否允许移动...const { x, y, width, height } = state.croppingImageAttr;if(width >= height && x + poorX <= 0 && x + poorX >= -width + state.croppingImageSize.width) {state.croppingImageAttr.x = x + poorX ;}else if(width < height && y + poorY <= 0 && y + poorY >= -height + state.croppingImageSize.height) {state.croppingImageAttr.y = y + poorY;}}}/** 将File类型转换为base64 */const getBase64ByFile = file => {return new Promise(resolve => {// 获取FileReader对象const reader = new FileReader() as any;// 读取完成后的回调reader.onload = ({ target: { result: src } }) => {// 创建图片对象const image = new Image();// 设置路径image.src = src;// 图片加载完成后image.onload = () => {// 记录裁剪图片的尺寸...let ratio = image.height >= image.width ? image.width / state.croppingImageSize.width : image.height / state.croppingImageSize.height;state.croppingImageAttr = {x: 0, y: 0, width: image.width / ratio, height: image.height / ratio};// 创建canvasconst canvas = document.createElement('canvas');// 设置canvas的宽高...canvas.width = image.width;canvas.height = image.height;// 获取上下文对象const context = canvas.getContext('2d') as any;// 绘制图片context.drawImage(image, 0, 0, image.width, image.height);// 返回图片的urlresolve(canvas.toDataURL(file.type));}};// 读取文件reader.readAsDataURL(file);});}/** 裁剪头像 */const croppingHeadImage = () => {return new Promise(resolve => {// 创建图片对象const image = new Image();// 设置路径image.src = state.croppingUrl;// 图片加载完成后image.onload = () => {// 获取裁剪的数据const { x: region_x, y: region_y, width, height } = state.croppingRegionAttr;const { x: img_x, y: img_y } = state.croppingImageAttr;// 图片压缩的比例let ratio = image.height >= image.width ? image.width / state.croppingImageSize.width : image.height / state.croppingImageSize.height;// 创建canvasconst canvas = document.createElement('canvas');// 设置canvas的宽高...canvas.width = width;canvas.height = height;// 获取上下文对象const context = canvas.getContext('2d') as any;// 绘制图片context.drawImage(image, (region_x + Math.abs(img_x)) * ratio, (region_y + Math.abs(img_y)) * ratio, width * ratio, height * ratio, 0, 0, canvas.width, canvas.height);// 得到base64const base64 = canvas.toDataURL(state.recordCroppingFile);// 转换文件...const buffer = atob(base64.split(',')[1]);let length = buffer.length;const bufferArray = new Uint8Array(new ArrayBuffer(length));while (length--) {bufferArray[length] = buffer.charCodeAt(length);}// 保存裁剪后的base64state.croppingAfterImage = base64;// 返回...resolve(new File([bufferArray], state.recordCroppingFile.name, { type: state.recordCroppingFile }));}});}/** 点击保存按钮 */const clickSave = async () => {const file = await croppingHeadImage();emit("croppingAfter", { file, base64: state.croppingAfterImage});state.croppingUrl = "";state.croppingAfterImage = "";destroy();}return {...toRefs(state),clickSave,}}
});
</script><style scoped lang="scss">.cutting {position: relative;overflow: hidden;&::-webkit-scrollbar {display: none;}img {position: absolute;}}#cropping-region {position: absolute;cursor: pointer;background-origin: content-box;background-image:linear-gradient(#212529 0.125rem, transparent 0.125rem, transparent calc(100% - 0.125rem), #212529 calc(100% - 0.125rem), #212529 100%),linear-gradient(90deg, #212529 2px, transparent 0.125rem, transparent calc(100% - 0.125rem), #212529 calc(100% - 0.125rem), #212529 100%),linear-gradient(#212529 0.125rem, transparent 0.125rem, transparent calc(100% - 0.125rem), #212529 calc(100% - 0.125rem), #212529 100%),linear-gradient(90deg, #212529 0.125rem, transparent 0.125rem, transparent calc(100% - 0.125rem), #212529 calc(100% - 0.125rem), #212529 100%);background-repeat: no-repeat;background-position: top left, top left, bottom right, bottom right;background-size: 0.625rem 100%, 100% 0.625rem;}#cropping-mask, .region {position: absolute;width: 100%;height: 100%;}#cropping-mask {background: rgba(255, 255, 255, .6);}.model {position: fixed;left: 50%;top: 50%;transform: translate(-50%, -50%);border-radius: 0.375rem;box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);padding: 1.25rem;}
</style>

使用:

<template><div style="margin-bottom: 10px;"><text>快上传:</text><input :value="data" @change="uploadFile" type="file" /></div><img :src="base64" alt="..." /><croppingImage @croppingAfter="croppingAfter" :file="file" />
</template><script lang="ts">
import { defineComponent, toRefs, reactive } from 'vue';
import croppingImage from "@/components/croppingImage/cropping-image.vue";export default defineComponent({components: {croppingImage,},setup() {const state = reactive({file: null as any,data: null,base64: "",});/** 上传图片 */const uploadFile = async event => {state.file = event.target.files[0];// 代码为了防止change事件同文件不触发,如果用组件库上传可以删除代码,忽略nextTick(() => {state.data = null;console.log(state.data)})}const croppingAfter = data => {state.file = null;state.base64 = data.base64;console.log(data);}return {...toRefs(state),uploadFile,croppingAfter,}}
});
</script><style scoped lang="scss"></style>

就这样!

使用canvas图片裁剪相关推荐

  1. canvas图片裁剪并base64转化

    一.需求 在canvas画布上,假设我们绘制了一张图片,现在需要将该图片进行裁剪,并将裁剪的区域提取成图像,并将该图像转化为base64格式返回,要求编写这样一个脚本. 二.知识储备 HTML5画布中 ...

  2. html图片自动剪裁,HTML canvas图像裁剪

    canvas drawImage方法的图像裁剪理解可能会比较耗时,记录一下,以便供人翻阅! context.drawImage(img,sx,sy,swidth,sheight,x,y,width,h ...

  3. (H5)canvas实现裁剪图片和马赛克功能,以及又拍云上传图片

    1.核心功能 此组件功能包含: 图片裁剪(裁剪框拖动,裁剪框改变大小): 图片马赛克(绘制马赛克,清除马赛克): 图片预览.图片还原(返回原图.返回处理图): 图片上传(获取签名.上传图片). 2.核 ...

  4. html5 手机端 剪裁,用Canvas实现h5移动端图片裁剪

    前阵子七夕的时候搞了一个h5活动页,需要做一个本地图片的裁剪上传功能,用于生成一些特定的表白图片.主要是用到了H5的FileApi 和 Canvas.个人感觉图片裁剪功能还是很实用的,故写篇文章分享一 ...

  5. 使用 canvas 居中裁剪图片

    在日常开发中,想必不少小伙伴有涉及到图片上传的功能,今天我们就来捋一捋上传图片时,如果图片大于规定尺寸,用 canvas 居中裁剪. 首先我们来熟悉一下 canvas 的 drawImage()的用法 ...

  6. jquery 图片裁剪 java_[Java教程]5 款最新的 jQuery 图片裁剪插件

    [Java教程]5 款最新的 jQuery 图片裁剪插件 0 2015-05-18 16:00:20 这篇文章主要介绍最新的 5 款 jQuery 图片裁剪插件,可以帮助你轻松的实现你网站需要的图像裁 ...

  7. 图片裁剪的js有哪些(整理)

    图片裁剪的js有哪些(整理) 一.总结 一句话总结:如果用了amaze框架就去amaze框架的插件库里面找图片裁剪插件,如果没用,jcrop和cropper都不错. 1.amazeui的插件库中有很多 ...

  8. 图片裁剪功能学习小结

    图片裁剪功能学习小结 近期有需要使用图片裁剪的功能,在使用插件和自己写裁剪组件之间犹豫了很久,后来根据需求经过反复的考虑,还是自己封装吧,毕竟自己动手,丰衣足食,对吧?嗯,??????是的!最后生成裁 ...

  9. 图片裁剪功能集成优化

    问题所在 最近公司编辑在发布新闻的时候遇到一个问题,编辑后台提供的原有的图片裁剪功能在移动端和一些特定类型的显示时达不到具体的要求.最后去深究发现我们的服务器对上传上来的图片进行了一次裁剪,为了减小图 ...

最新文章

  1. 图论 ---- DAG删点+枚举+暴力+离线前缀异或和 J Red-Black Paths (2021 icpc网络赛第一场)
  2. 【转】Hibernate的Generator属性有7种class,本文简略描述了这7种class的意义和用法。...
  3. java静态方法 问题_Java中堆、栈,静态方法和非静态方法的速度问题
  4. reactjs回调函数形式的ref:含内联形式回调函数调用次数问题
  5. 如何在Windows 8.1中获取Windows 10样式的开始菜单
  6. 数据结构开发(6):静态单链表的实现
  7. 九.jmeter性能测试基础实践(1)
  8. python3标识符_python3学习笔记一(标识符、关键字)
  9. WPF笔记汇总之ListView控件
  10. 51单片机IIC驱动OLED
  11. Bridge2021有什么功能?Br 2021 新增功能介绍
  12. HTTP 请求 报错信息406
  13. 语音识别-食物声音识别
  14. npm cb() never called!和 Error: getaddrinfo ENOTFOUND registry.npmjs.com registry.npmjs.com:443
  15. arnold渲染器预览窗口打开时保存有可能崩溃,解决方法如下
  16. [cv]郑哲东 Deep-ReID——Learn pedestrian representations from
  17. 足球相关的英文专业术语(持续更新中...Ctrl+F可直接进行搜索)
  18. 金华职业技术学院计算机教研室主任,机械技术系主任及教师赴金华职业技术学院走访调研...
  19. Excel 将文本格式快速转换为数值格式
  20. 有关国内的流氓软件和强制安装软件(上传附件防部分流氓软件)

热门文章

  1. 骑行蓝牙耳机哪个好、适合骑车时候戴的骨传导耳机
  2. 【初学者入门】零基础入门NLP - 新闻文本分类
  3. [C++11] auto关键字详解
  4. 人狠话不多!阿里成立半导体公司「平头哥」:首款 AI 芯片明年面世
  5. 怎么设置计算机访问需要密码图片,电脑桌面上有一个文件夹需要加密。就是设置成访问时输密码,怎么弄?...
  6. [转载]GB2312简体中文编码表
  7. 美团:建议通过“买药”专属频道购买,正在严打“虚假代购”行为
  8. PaddleX数据标注与Halcon数据标注与转换
  9. 【无2021年危险化学品生产单位安全生产管理人员考试题及危险化学品生产单位安全生产管理人员免费试题
  10. CodeForce-1196D1-RGB Substring (easy version)