西安微信小程序制作_vue完成裁切图片同时完成扩大、缩小、旋转

vue实现裁切图片同时实现放大、缩小、旋转功能       这篇文章主要介绍了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 !!(pareDocumentPosition(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 : 
 canvas ref="ctx"
 :width="clip.width"
 :height="clip.height"
 @mousedown="handleClip($event)"
 /canvas 
 input type="file" ref="file" @change="readFileMsg($event)" 
 div 
 a @click="handleScale(false)" + /a 
 a @click="rotate" 转 /a 
 a @click="handleScale(true)" - /a 
 span {{scale}} /span 
 /div 
 div 
 a @click="dispatchUpload($event)" upload /a 
 a cancel /a 
 /div 
 div 
 a @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,
 pro凡科抠图: {
 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;
 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;
 }

项目代码:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持凡科。