好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

原文链接:https://segmentfault.com/a/1190000013473331

查看更多关于vue实现裁切图片同时实现放大、缩小、旋转功能的详细内容...

  阅读:49次