欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-03 10:42:03
本篇文章主要介绍了vue实现裁切图片同时实现放大、缩小、旋转功能,分享给大家,具体如下: 实现效果: 裁切指定区域内的图片 旋转图片 放大图片...

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

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