vue实现裁切图片同时实现放大、缩小、旋转功能
程序员文章站
2022-06-03 10:42:03
本篇文章主要介绍了vue实现裁切图片同时实现放大、缩小、旋转功能,分享给大家,具体如下:
实现效果:
裁切指定区域内的图片
旋转图片
放大图片...
本篇文章主要介绍了vue实现裁切图片同时实现放大、缩小、旋转功能,分享给大家,具体如下:
实现效果:
- 裁切指定区域内的图片
- 旋转图片
- 放大图片
- 输出bolb 格式数据 提供给 formdata 对象
效果图
大概原理:
利用h5 filereader 对象, 获取 <input type="file"/> “上传到浏览器的文件” ,文件形式 为base64形式, 把 base64 赋给canvas的上下文。
然后给canvas 元素上加入对(mousedown)监听事件。 当用户鼠标左键在canvas按下时:
- 挂载对 window 对象mousemove事件 ---> 获取 鼠标移动x,y距离.从而操作 canvas里的图像的位置移动。
- 挂载对 window 对象mouseup 事件, 清除 mousemove事件的绑定。(同时该事件触发后会被删除)
剩下的 放大、缩小 、 旋转 是对 canvas 对象的操作/坐标体系的操作。具体api详见mdn canvas 文档
代码
dom.js
export const on = ({el, type, fn}) => { if (typeof window) { if (window.addeventlistener) { el.addeventlistener(type, fn, false) } else { el.attachevent(`on${type}`, fn) } } } export const off = ({el, type, fn}) => { if (typeof window) { if (window.addeventlistener) { el.removeeventlistener(type, fn) } else { el.detachevent(`on${type}`, fn) } } } export const once = ({el, type, fn}) => { const hyfn = (event) => { try { fn(event) } finally { off({el, type, fn: hyfn}) } } on({el, type, fn: hyfn}) } // 最后一个 export const fbtwice = ({fn, time = 300}) => { let [ctime, k] = [null, null] // 获取当前时间 const gettime = () => new date().gettime() // 混合函数 const hyfn = () => { const ags = argments return () => { cleartimeout(k) k = ctime = null fn(...ags) } } return () => { if (ctime == null) { k = settimeout(hyfn(...arguments), time) ctime = gettime() } else { if ( gettime() - ctime < 0) { // 清除之前的函数堆 ---- 重新记录 cleartimeout(k) k = null ctime = gettime() k = settimeout(hyfn(...arguments), time) } }} } export const contains = function(parentnode, childnode) { if (parentnode.contains) { return parentnode != childnode && parentnode.contains(childnode) } else { return !!(parentnode.comparedocumentposition(childnode) & 16) } } export const addclass = function (el, classname) { if (typeof el !== "object") { console.log('el is not elem') return null } let classlist = el['classname'] classlist = classlist === '' ? [] : classlist.split(/\s+/) if (classlist.indexof(classname) === -1) { classlist.push(classname) el.classname = classlist.join(' ') } else { console.warn('warn classname current') } } export const removeclass = function (el, classname) { let classlist = el['classname'] classlist = classlist === '' ? [] : classlist.split(/\s+/) classlist = classlist.filter(item => { return item !== classname }) el.classname = classlist.join(' ') } export const delay = ({fn, time}) => { let ot = null let k = null return () => { // 当前时间 let ct = new date().gettime() const fixfn = () => { k = ot = null fn() } if (k === null) { ot = ct k = settimeout(fixfn, time) return } if (ct - ot < time) { ot = ct cleartimeout(k) k = settimeout(fixfn, time) } } } export const event = function () { // 类型 this.typelist = {} } event.prototype.on = function ({type, fn}){ if (this.typelist.hasownproperty(type)) { this.typelist[type].push(fn) } else { this.typelist[type] = [] this.typelist[type].push(fn) } } event.prototype.off = function({type, fn}) { if (this.typelist.hasownproperty(type)) { let list = this.typelist[type] let index = list.indexof(fn) if (index !== -1 ) { list.splice(index, 1) } } else { console.warn('not has this type') } } event.prototype.once = function ({type, fn}) { const fixfn = () => { fn() this.off({type, fn: fixfn}) } this.on({type, fn: fixfn}) } event.prototype.trigger = function (type){ if (this.typelist.hasownproperty(type)) { this.typelist[type].foreach(fn => { fn() }) } }
组件模板
<template> <div class="jc-clip-image" :style="{width: `${clip.width}`}"> <canvas ref="ctx" :width="clip.width" :height="clip.height" @mousedown="handleclip($event)" > </canvas> <input type="file" ref="file" @change="readfilemsg($event)"> <div class="clip-scale-btn"> <a class="add" @click="handlescale(false)">+</a> <a @click="rotate" class="right-rotate">转</a> <a class="poor" @click="handlescale(true)">-</a> <span>{{scale}}</span> </div> <div class="upload-warp"> <a class="upload-btn" @click="dispatchupload($event)">upload</a> <a class="upload-cancel">cancel</a> </div> <div class="create-canvas"> <a class="to-send-file" @click="outfile" title="请打开控制台">生成文件</a> </div> </div> </template> <script> import {on, off, once} from '../../utils/dom' export default { ctx: null, file: null, x: 0, // 点击canvas x 鼠标地址 y: 0,// 点击canvas y 鼠标地址 xv: 0, // 鼠标移动 x距离 yv: 0, // 鼠标移动 y距离 nx: 0, // 原始坐标点 图像 x ny: 0,// 原始坐标点 图像 y img: null, props: { src: { type: string, default: null }, clip: { type: object, default () { return {width: '200px', height: '200px'} } } }, data () { return { isshow: false, base64: null, scale: 1.5, //放大比例 deg: 0 //旋转角度 } }, computed: { width () { const {clip} = this return parsefloat(clip.width.replace('px', '')) }, height () { const {clip} = this return parsefloat(clip.height.replace('px', '')) } }, mounted () { const {$options, $refs, width, height} = this // 初始化 canvas file nx ny object.assign($options, { ctx: $refs.ctx.getcontext('2d'), file: $refs.file, nx: -width / 2, ny: -height / 2 }) }, methods: { // 旋转操作 rotate () { const {$options, draw} = this this.deg = (this.deg + math.pi /2)% (math.pi * 2) draw($options.img, $options.nx + $options.xv, $options.ny + $options.yv, this.scale, this.deg) }, // 处理放大 handlescale (flag) { const {$options, draw, deg} = this flag && this.scale > 0.1 && (this.scale = this.scale - 0.1) !flag && this.scale < 1.9 && (this.scale = this.scale + 0.1) $options.img && draw($options.img, $options.nx + $options.xv, $options.ny + $options.yv, this.scale, deg) }, // 模拟file 点击事件 dispatchupload (e) { this.clearstate() const {file} = this.$options e.preventdefault() file.click() }, // 读取 input file 信息 readfilemsg () { const {file} = this.$options const {draw, createimage, $options: {nx, ny}, scale, deg} = this const wfile = file.files[0] const reader = new filereader() reader.onload = (e) => { const img = createimage(e.target.result, (img) => { draw(img, nx, ny, scale, deg) }) file.value = null } reader.readasdataurl(wfile) }, // 生成 图像 createimage (src, cb) { const img = new image() this.$el.append(img) img.classname = 'base64-hidden' img.onload = () => { cb(img) } img.src = src this.$options.img = img }, // 操作画布画图 draw (img, x = 0, y = 0, scale = 0.5,deg = math.pi ) { const {ctx} = this.$options let {width, height} = this // 图片尺寸 let imgw = img.offsetwidth let imgh = img.offsetheight ctx.save() ctx.clearrect( 0, 0, width, height) ctx.translate( width / 2, height / 2, img) ctx.rotate(deg) ctx.drawimage(img, x, y, imgw * scale, imgh * scale) ctx.restore() }, // ... 事件绑定 handleclip (e) { const {handlemove, $options, deg} = this if (!$options.img) { return } object.assign(this.$options, { x: e.screenx, y: e.screeny }) on({ el: window, type: 'mousemove', fn: handlemove }) once({ el: window, type: 'mouseup', fn: (e) =>{ console.log('down') switch (deg) { case 0: { object.assign($options, { nx: $options.nx + $options.xv, ny: $options.ny + $options.yv, xv: 0, yv: 0 }) break; } case math.pi / 2: { object.assign($options, { nx: $options.ny + $options.yv, ny: $options.nx - $options.xv, xv: 0, yv: 0 }) break; } case math.pi: { object.assign($options, { nx: $options.nx - $options.xv, ny: $options.ny - $options.yv, xv: 0, yv: 0 }) break; } default: { // $options.ny - $options.yv, $options.nx + $options.xv object.assign($options, { nx: $options.ny - $options.yv, ny: $options.nx + $options.xv, xv: 0, yv: 0 }) } } off({ el: window, type: 'mousemove', fn: handlemove }) } }) }, // ... 处理鼠标移动 handlemove (e){ e.preventdefault() e.stoppropagation() const {$options, draw, scale, deg} = this object.assign($options, { xv: e.screenx - $options.x, yv: e.screeny - $options.y }) switch (deg) { case 0: { draw($options.img, $options.nx + $options.xv, $options.ny + $options.yv, scale, deg) break; } case math.pi / 2: { draw($options.img, $options.ny + $options.yv, $options.nx - $options.xv, scale, deg) break; } case math.pi: { draw($options.img, $options.nx - $options.xv, $options.ny - $options.yv, scale, deg) break; } default: { draw($options.img, $options.ny - $options.yv, $options.nx + $options.xv, scale, deg) break; } } }, // 清除状态 clearstate () { const {$options, width, height} = this if ($options.img) { this.$el.removechild($options.img) object.assign($options, { x: 0, y: 0, xv: 0, yv: 0, nx: -width / 2, ny: -height / 2, img: null, }) } }, // 输出文件 outfile () { const {$refs: {ctx}} = this console.log(ctx.todataurl()) ctx.toblob((blob) => {console.log(blob)}) } } } </script> <style> @component-namespace jc { @component clip-image{ position: relative; width: 100%; canvas { position: relative; width: 100%; height: 100%; cursor: pointer; box-shadow: 0 0 3px #333; } input { display: none; } .base64-hidden { position: absolute; top: 0; left: 0; display: block; width: 100%; height: auto; z-index: -999; opacity: 0; } .clip-scale-btn { position: relative; @utils-clearfix; margin-bottom: 5px; text-align: center; a { float: left; width: 20px; height: 20px; border-radius: 50%; color: #fff; background: #49a9ee; text-align: center; cursor: pointer; } &>.poor, &>.right-rotate { float: right; } &>span{ position: absolute; z-index: -9; top: 0; left: 0; display: block; position: relative; width: 100%; text-align: center; height: 20px; line-height: 20px; } } .upload-warp { @utils-clearfix; .upload-btn,.upload-cancel { float: left; display:inline-block; width: 60px; height: 25px; line-height: 25px; color: #fff; border-radius: 5px; background: #49a9ee; box-shadow: 0 0 0 #333; text-align: center; top: 0; left: 0; right: 0; bottom: 0; margin: auto; cursor: pointer; margin-top: 5px; } .upload-cancel{ background: gray; float: right; } } .to-send-file { margin-top: 5px; display: block; width: 50px; height: 25px; line-height: 25px; color: #fff; border-radius: 5px; background: #49a9ee; cursor: pointer; } }
项目代码:https://github.com/l6zt/vuesrr
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。