vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能-創(chuàng)新互聯(lián)

這篇文章將為大家詳細(xì)講解有關(guān)vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

成都創(chuàng)新互聯(lián)公司-專(zhuān)業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性?xún)r(jià)比惠濟(jì)網(wǎng)站開(kāi)發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式惠濟(jì)網(wǎng)站制作公司更省心,省錢(qián),快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋惠濟(jì)地區(qū)。費(fèi)用合理售后完善,10余年實(shí)體公司更值得信賴(lài)。

vue是什么

Vue是一套用于構(gòu)建用戶(hù)界面的漸進(jìn)式JavaScript框架,Vue與其它大型框架的區(qū)別是,使用Vue可以自底向上逐層應(yīng)用,其核心庫(kù)只關(guān)注視圖層,方便與第三方庫(kù)和項(xiàng)目整合,且使用Vue可以采用單文件組件和Vue生態(tài)系統(tǒng)支持的庫(kù)開(kāi)發(fā)復(fù)雜的單頁(yè)應(yīng)用。

vue實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能,分享給大家,具體如下:

實(shí)現(xiàn)效果:

  1. 裁切指定區(qū)域內(nèi)的圖片

  2. 旋轉(zhuǎn)圖片

  3. 放大圖片

  4. 輸出bolb 格式數(shù)據(jù) 提供給 formData 對(duì)象

效果圖

vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能

vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能

vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能

vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能

vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能

vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能

vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能

大概原理:

利用h6 FileReader 對(duì)象, 獲取 <input type="file"/> “上傳到瀏覽器的文件” ,文件形式 為base64形式, 把 base64 賦給canvas的上下文。

然后給canvas 元素上加入對(duì)(mousedown)監(jiān)聽(tīng)事件。 當(dāng)用戶(hù)鼠標(biāo)左鍵在canvas按下時(shí):

  1. 掛載對(duì) window 對(duì)象mousemove事件 ---> 獲取 鼠標(biāo)移動(dòng)x,y距離.從而操作 canvas里的圖像的位置移動(dòng)。

  2. 掛載對(duì) window 對(duì)象mouseup 事件, 清除 mousemove事件的綁定。(同時(shí)該事件觸發(fā)后會(huì)被刪除)

剩下的 放大、縮小 、 旋轉(zhuǎn) 是對(duì) canvas 對(duì)象的操作/坐標(biāo)體系的操作。具體api詳見(jiàn)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})
  }
  // 最后一個(gè)
  export const fbTwice = ({fn, time = 300}) => {
    let [cTime, k] = [null, null]
    // 獲取當(dāng)前時(shí)間
    const getTime = () => new Date().getTime()
    // 混合函數(shù)
    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) {
          // 清除之前的函數(shù)堆 ---- 重新記錄
          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 () => {
      // 當(dāng)前時(shí)間
      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 () {
    // 類(lèi)型
    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" :>
    <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">轉(zhuǎn)</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="請(qǐng)打開(kāi)控制臺(tái)">生成文件</a>
    </div>
  </div>
</template>
<script>
  import {on, off, once} from '../../utils/dom'
  export default {
    ctx: null, 
    file: null, 
    x: 0, // 點(diǎn)擊canvas x 鼠標(biāo)地址
    y: 0,// 點(diǎn)擊canvas y 鼠標(biāo)地址
    xV: 0, // 鼠標(biāo)移動(dòng) x距離
    yV: 0, // 鼠標(biāo)移動(dòng) y距離
    nX: 0, // 原始坐標(biāo)點(diǎn) 圖像 x
    nY: 0,// 原始坐標(biāo)點(diǎn) 圖像 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 //旋轉(zhuǎn)角度
    }
    },
    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: {
    // 旋轉(zhuǎn)操作
      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 點(diǎn)擊事件
      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
      },
      // 操作畫(huà)布畫(huà)圖
      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
          })
          }
        })
      },
      // ... 處理鼠標(biāo)移動(dòng)
      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;
          }
        }
      },
      // 清除狀態(tài)
      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;
      }
    }

關(guān)于“vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性?xún)r(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專(zhuān)為企業(yè)上云打造定制,能夠滿(mǎn)足用戶(hù)豐富、多元化的應(yīng)用場(chǎng)景需求。

網(wǎng)頁(yè)名稱(chēng):vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能-創(chuàng)新互聯(lián)
標(biāo)題路徑:http://muchs.cn/article4/cosgie.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動(dòng)態(tài)網(wǎng)站、做網(wǎng)站、網(wǎng)站營(yíng)銷(xiāo)、品牌網(wǎng)站建設(shè)、微信小程序、云服務(wù)器

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化