vue 完成 裁切图片 同时有放大、减少、扭转功用

完成结果:

  1. 裁切指定区域内的图片
  2. 扭转图片
  3. 放大图片
  4. 输出bolb 花样数据 提供给 formData 对象

结果图

《vue 完成 裁切图片 同时有放大、减少、扭转功用》
《vue 完成 裁切图片 同时有放大、减少、扭转功用》
《vue 完成 裁切图片 同时有放大、减少、扭转功用》
《vue 完成 裁切图片 同时有放大、减少、扭转功用》
《vue 完成 裁切图片 同时有放大、减少、扭转功用》
《vue 完成 裁切图片 同时有放大、减少、扭转功用》
《vue 完成 裁切图片 同时有放大、减少、扭转功用》

也许道理:

应用h5 FileReader 对象, 猎取 <input type=”file”/> “上传到浏览器的文件” ,文件情势 为base64情势, 把 base64 赋给canvas的上下文。
然后给canvas 元素上到场对(mousedown)监听事宜。 当用户鼠标左键在canvas按下时:

  1. 挂载对 window 对象mousemove事宜 —> 猎取 鼠标挪动x,y间隔.从而操纵 canvas里的图象的位置挪动。
  2. 挂载对 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;
            }
        }
    }
</style>

项目代码(https://github.com/L6zt/vuesrr

    原文作者:L6zt
    原文地址: https://segmentfault.com/a/1190000013473331
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞