vue实现裁切图片同时实现放大、缩小、旋转功能


Posted in Javascript onMarch 02, 2018

本篇文章主要介绍了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;
      }
    }

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

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

Javascript 相关文章推荐
jquery tools 系列 scrollable(2)
Sep 06 Javascript
javascript中利用数组实现的循环队列代码
Jan 24 Javascript
jQuery温习篇 强大的JQuery选择器
Apr 24 Javascript
jquery $(&quot;#variable&quot;) 循环改变variable的值示例
Feb 23 Javascript
一个JS函数搞定网页标题(title)闪动效果
May 13 Javascript
JQuery设置时间段下拉选择实例
Dec 30 Javascript
微信小程序 生命周期和页面的生命周期详细介绍
Jan 19 Javascript
Node.js发送HTTP客户端请求并显示响应结果的方法示例
Apr 12 Javascript
利用Mongoose让JSON数据直接插入或更新到MongoDB
May 03 Javascript
20个最常见的jQuery面试问题及答案
May 23 jQuery
vue实现未登录跳转到登录页面的方法
Jul 17 Javascript
微信小程序地图绘制线段并且测量(实例代码)
Jan 02 Javascript
vue slot 在子组件中显示父组件传递的模板
Mar 02 #Javascript
vue2.0 自定义 饼状图 (Echarts)组件的方法
Mar 02 #Javascript
vue 使用eventBus实现同级组件的通讯
Mar 02 #Javascript
详解node.js 下载图片的 2 种方式
Mar 02 #Javascript
vue2.0+vue-dplayer实现hls播放的示例
Mar 02 #Javascript
详解vue2.0+vue-video-player实现hls播放全过程
Mar 02 #Javascript
vue2.0 + element UI 中 el-table 数据导出Excel的方法
Mar 02 #Javascript
You might like
上传文件先创建目录 再上传到目录里面去
2010/12/29 PHP
Zend Framework教程之Application和Bootstrap用法详解
2016/03/10 PHP
PHP处理CSV表格文件的常用操作方法总结
2016/07/01 PHP
php的api数据接口书写实例(推荐)
2016/09/22 PHP
JS加jquery简单实现标签元素的显示或隐藏
2013/09/23 Javascript
javascript匿名函数应用示例介绍
2014/03/07 Javascript
事件委托与阻止冒泡阻止其父元素事件触发
2014/09/02 Javascript
jQuery中addClass()方法用法实例
2015/01/05 Javascript
javascript如何定义对象数组
2016/06/07 Javascript
jquery实现文字单行横移或翻转(上下、左右跳转)
2017/01/08 Javascript
webpack独立打包和缓存处理详解
2017/04/03 Javascript
javascript 中的继承实例详解
2017/05/05 Javascript
实例详解JavaScript中setTimeout函数的执行顺序
2017/07/12 Javascript
JS如何设置元素样式的方法示例
2017/08/28 Javascript
React Native 图片查看组件的方法
2018/03/01 Javascript
浅谈Redux中间件的实践
2018/07/27 Javascript
CryptoJS中AES实现前后端通用加解密技术
2018/12/18 Javascript
JS实现烟花爆炸效果
2020/03/10 Javascript
python根据路径导入模块的方法
2014/09/30 Python
在Python中封装GObject模块进行图形化程序编程的教程
2015/04/14 Python
Python针对给定列表中元素进行翻转操作的方法分析
2018/04/27 Python
对python GUI实现完美进度条的示例详解
2018/12/13 Python
使用TensorFlow实现二分类的方法示例
2019/02/05 Python
python删除文件夹下相同文件和无法打开的图片
2019/07/16 Python
python匿名函数的使用方法解析
2019/10/10 Python
ffmpeg+Python实现B站MP4格式音频与视频的合并示例代码
2020/10/21 Python
利用Python pandas对Excel进行合并的方法示例
2020/11/04 Python
西班牙家用电器和电子产品购物网站:Mi Electro
2019/02/25 全球购物
印度尼西亚最完整和最大的在线药房网站:Farmaku.com
2019/11/23 全球购物
彪马法国官网:PUMA法国
2019/12/15 全球购物
环保建议书
2014/03/12 职场文书
俞敏洪北大演讲稿
2014/05/22 职场文书
2014年外联部工作总结
2014/11/17 职场文书
2015年教师师德师风承诺书
2015/04/28 职场文书
导游词之香港-太平山顶
2019/10/18 职场文书
python实现手机推送 代码也就10行左右
2022/04/12 Python