使用element-ui + cropper.js自己封裝一個圖片裁剪組件

本文主要使用了element-ui的圖片上傳組件獲取圖片,然后使用cropper.js進行圖片裁剪,在裁剪完以后進行格式轉(zhuǎn)換,然后自行圖片上傳,文中的css使用了stylus編譯,如果不是請自行更改

裁剪效果如圖

1.插件安裝

npm install cropperjs --save

element-ui的安裝就不予以贅述了,主要使用了element-ui的圖片上傳以及$message

2.組件編寫

  • 目錄新建

components文件夾下新建upload文件夾,然后在upload文件夾下新建 cropperBox.vue 以及 upCover.vue

  • 編寫cropperBox.vue

cropperBox.vue中寫入以下內(nèi)容,(finishCropImage方法中的blob的數(shù)據(jù)格式請自行選擇以下兩種,第一種是blob,第二種是普通文件方式。)

<template>
  <div class="cropper-wrap">
    <div class="cropper-alert-mask" :class="{ show: imgHasLoad }"></div>
    <div class="cropper-alert" :class="{ show: imgHasLoad }">
      <div class="cropper">
        <span class="layout-icon-wrap"><i class="el-icon-circle-close" @click="imgHasLoad=false"></i></span>
        <div class="cropper-box">
          <img ref="uploadPreview" style="width:100px;height:auto;">
        </div>
        <div class="cropper-res-wrap">
          <div class="cropper-res" id="cropperRes">
            <img style="width:100px;height:100px;">
          </div>
        </div>
      </div>
      <div class="cropper-btns-wrap">
        <el-progress
          :text-inside="true"
          :stroke-width="30"
          :percentage="uploadProgress">
        </el-progress>
        <button
          type="button"
          class="cropper-btn"
          @click="finishCropImage"
          :disabled="btnTips.disable"
          :class="{'btn-bg': uploading}">
          {{ btnTips.value }}
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import Cropper from 'cropperjs'

export default {
  name: 'cropper-box',
  props: {
    options: {
      default: {
        aspectRatio: 1 / 1,
        preview: '#cropperRes',
        zoomOnWheel: false,
        minCropBoxWidth: 50
      }
    },
    uploadProgress: {
      default: 0
    }
  },
  data () {
    return {
      cropper: null,
      imgHasLoad: false,
      cropperHasInit: false,
      uploading: false,
      rawFile: null
    }
  },
  watch: {
    imgHasLoad (val) {
      if (!val) {
        this.uploading = false
      }
    }
  },
  computed: {
    btnTips () {
      if (this.uploading) {
        return {
          value: '正在上傳,請稍等',
          disable: true
        }
      }
      return {
        value: '裁剪完成,立即上傳',
        disable: false
      }
    }
  },
  methods: {
    show () {
      this.imgHasLoad = true
    },
    close () {
      this.imgHasLoad = false
    },
    loadCropper (rawFile) {
      this.rawFile = rawFile
      const URL = window.URL || window.webkitURL
      const blobURL = URL.createObjectURL(rawFile)
      var image = this.$refs.uploadPreview
      if (!this.cropper) this.cropper = new Cropper(image, this.options)
      this.cropper.reset().replace(blobURL)
    },
    // 完成裁剪,將文件進行格式轉(zhuǎn)換,發(fā)送給父組件,請自行選擇普通文件格式還是blob格式,格式轉(zhuǎn)換方法已封裝,請看下面的兩個方法
    finishCropImage () {
      this.uploading = true

      const croppedCanvas = this.cropper.getCroppedCanvas()
      const croppedDataUrl = croppedCanvas.toDataURL(this.rawFile.type)
      const blob = this.base64toFile(croppedDataUrl)
      this.$emit('finishCropImage', blob)
    },
    // dataUrl 轉(zhuǎn) blob
    dataURLtoBlob (dataurl) {
      /* eslint-disable */
      var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1];
      var bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
      while(n--){
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], {type:mime});
      /* eslint-enable */
    },
    //base64轉(zhuǎn)為普通文件格式
    base64toFile (dataurl, filename = 'file') {
      let arr = dataurl.split(',')
      let mime = arr[0].match(/:(.*?);/)[1]
      let suffix = mime.split('/')[1]
      let bstr = atob(arr[1])
      let n = bstr.length
      let u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], `${filename}.${suffix}`, {
        type: mime
      })
    },
  }
}
</script>

<style lang="stylus">
  @import "cropperjs/dist/cropper.min.css"

.cropper-wrap
  .cropper-alert-mask
    position: fixed
    top: 0
    left: 0
    right: 0
    bottom: 0
    z-index: 2000
    background-color: rgba(#000000, .5)
    visibility: hidden
    height: 0
    transition: all .3s ease
  .cropper-alert-mask.show
    visibility: visible
    height: 100%
  .cropper-alert
    opacity: 0
    transition: all .3s ease
    visibility: hidden
    padding: 10px
    position: fixed
    z-index: 2000
    top: 50px
    left: 50%
    transform: translateX(-50%) scale(2)
    background-color: #ffffff
    border-radius: 5px
    overflow: hidden
    width: 100%
    height: 100%
    max-width: 600px
    max-height: 530px
    &.show
      opacity: 1
      visibility: visible
      transform: translateX(-50%) scale(1)
    .cropper
      position: relative
      max-width: 600px
      max-height: 460px
      height: 100%
      padding: 10px
      padding-right: 120px
      @media (max-width: 1324px)
        padding-top: 120px
        padding-right: 10px
      background-color: #f9fbfc
      .layout-icon-wrap
        position: absolute
        cursor: pointer
        right: 0px
        top: 0px
        font-size: 20px
      .cropper-box
        position: relative
        width: 100%
        height: 100%
        background-color: #ffffff
      .cropper-res-wrap
        position: absolute
        top: 50%
        transform: translateY(-50%)
        @media (max-width: 1324px)
          top: 0
          left: 50%
          transform: translateX(-50%)
        right: 0
        width: 100px
        height: auto
        padding: 10px
        background-color: #a8a8a8
        box-sizing: content-box
        .cropper-res
          width: 100px
          height: 100px
          overflow: hidden
          background-color: #ffffff
    .cropper-btns-wrap
      position: relative
      margin-top: 20px
      .cropper-btn
        position: absolute
        left: 0
        top: 0
        width: 100%
        height: 30px
        line-height: 1
        background: #ffffff
        border: 1px solid #e1e1e1
        border-radius: 15px
        color: #666666
        cursor: pointer
      .btn-bg
        background: #FF000000
</style>
  • 編寫upCover.vue

upCover.vue中寫入以下內(nèi)容,(finishCropImage方法中請自行補充圖片上傳調(diào)用接口的方法)

<template>
  <div
    class="cover-upload-wrap"
    ref=coverOutWrap
    :style="{
      width: width ? (width + 'px') : '100%',
      height: calcHeight + 'px',
      maxWidth: maxWidth ? (maxWidth + 'px') : '100px',
      maxHeight: maxHeight ? (maxHeight + 'px') : '100px'
    }">
    <el-upload
      ref="upload"
      class="cover-uploader"
      :show-file-list="false"
      :before-upload="beforeVipImageUpload"
      :auto-upload="false"
      :on-change="onFileChange"
      :on-progress="onUploadProgress">
      <div class="img-wrap">
        <img :src="imageUrl" class="cover" v-if="imageUrl">
        <div class="img-mask-default" :class="{'img-mask': imageUrl}">
          <i class="el-icon-upload"></i>
          <div>{{ tip }}</div>
        </div>
      </div>
    </el-upload>
    <cropperBox
      ref="cropperBox"
      :options="options"
      :uploadProgress="uploadProgress"
      @finishCropImage="finishCropImage">
    </cropperBox>
  </div>
</template>
<script>
import uploadImage from "../../mixins/uploadImage";
import cropperBox from './cropperBox'
import {upload} from "../../api/base";

export default {
  name: 'up-cover',
  mixins:[uploadImage],
  components: {
    cropperBox
  },
  props: {
    defaultImg: String,
    ratio: { // 裁剪結(jié)果寬高比
      default: 1
    },
    width: [Number,String],
    height: [Number,String],
    WHRatio: { // 組件寬高比
      default: 1
    },
    cropBoxResizable:Boolean,    //是否可以修改裁剪框的尺寸
    maxWidth: String,
    maxHeight: String,
    tip: {
      default: '上傳圖片'
    },
    maxSize: { // 最大選擇圖片的大小,單位M
      default: 3
    }
  },
  data () {
    return {
      cropper: null,
      newFile: null,
      options: {
        aspectRatio: 2.166,
        preview: '#cropperRes',
        zoomOnWheel: false,
        cropBoxResizable:false,
        minCropBoxWidth: 50,
        viewMode:3
      },
      token: {},
      uploadProgress: 0,
      calcHeight: 0
    }
  },
  created() {
    this.options.aspectRatio = this.ratio;
    this.options.cropBoxResizable = this.cropBoxResizable;
    if (this.height) {
      this.calcHeight = this.height
    }
  },
  computed: {
    imageUrl () {
      return this.defaultImg
    }
  },
  mounted() {
    if (!this.calcHeight) {
      if (this.width) {
        this.calcHeight = this.width / this.WHRatio
      } else {
        this.calcHeight = this.$refs.coverOutWrap.offsetWidth / this.WHRatio
      }
    }
  },
  methods: {
    //截取圖片上傳的事件,有圖片的情況就打開裁剪框,并且將文件傳入到裁剪框中
    onFileChange (file, fileList) {
      if (file.status === 'ready') {
        this.$refs.cropperBox.show();
        this.$refs.cropperBox.loadCropper(file.raw)
      }
    },
    //文件裁剪,nerFile就是最終拿到的文件,自行調(diào)接口進行圖片上傳
    finishCropImage (newFile) {
      this.newFile = newFile;
      //圖片上傳調(diào)接口請自行補充
    },
    //上傳圖片之前的大小檢測,文件大小通過prop傳遞,可在組件使用自定義文件大小限制
    beforeVipImageUpload(file){
        const isLt3M = file.size / 1024 / 1024 < this.maxSize;
        if (!isLt3M) {
            this.$message.error('上傳圖片大小不可大于' + this.maxSize + 'M');
        }
        return isLt3M;
        let uploadFile = new window.File([this.newFile], file.name, { type: this.newFile.type });
        uploadFile.uid = this.newFile.uid;
        return Promise.resolve(uploadFile)
    },
    //文件上傳進度
    onUploadProgress (event, file, fileList) {
      this.uploadProgress = parseInt(event.percent) - 1
    }
  }
}
</script>
<style lang="stylus">
.cover-upload-wrap
  position: relative
  width: 100%
  max-width: 300px
  height: 150px
  border-radius: 5px
  .cover-uploader
    width: 100%
    height: 100%
    .el-upload
      width: 100%
      height: 100%
      overflow: hidden
      border-radius: 5px
      cursor: pointer
      border: 1px solid #dddddd
      .img-wrap
        position: relative
        width: 100%
        height: 100%
        &:hover
          .img-mask-default
            opacity: 1
            background-color: rgba(0, 0, 0, 0.5)
            color: #ffffff
        .cover
          position: relative
          width: 100%
          height: 100%
          border-radius: 5px
        .img-mask-default
          position: absolute
          left: 0
          top: 0
          width: 100%
          height: 100%
          padding-left: 10px
          padding-right: 10px
          background-color: #ffffff
          color: #555555
          display: flex
          flex-direction: column
          justify-content: center
          font-size: 12px
          transition: all .2s linear
          .el-icon-upload
            font-size: 18px
        .img-mask
          opacity: 0

</style>

使用方法

  • 引入插件
import UP from '@/components/upload/upCover'

export default {
      components:{UP},
}
  • 在頁面中使用

pictureUrl就是圖片上傳成功以后顯示的回調(diào)地址,請自行填寫,maxSize可以限定上傳圖片時候的尺寸大小,width和height限定組件大小,

<UP class="upload-cover"
    :default-img="pictureUrl?pictureUrl:''"
    ratio="2.166"
    :cropBoxResizable="true"
    width="156"
    height="72"
    tip="上傳等級圖標"
    maxSize="3"
    @uploadSuccess="uploadSuccess">
</UP>

uploadSuccess方法中可以拿到圖片上傳成功以后的回調(diào),具體操作自行補充啦

uploadSuccess(res){
    //this.pictureUrl=res.data
},

更多操作

javascript操作

this.myCropper.getCroppedCanvas().toDataURL('image/jpeg') //拿到裁剪后的base64的圖片
this.myCropper.getCropBoxData();    //獲取裁剪框數(shù)據(jù)
this.myCropper.setCropBoxData();    //設(shè)置裁剪框數(shù)據(jù)
this.myCropper.getCanvasData();      //獲取圖片數(shù)據(jù)
this.myCropper.setCanvasData();      //設(shè)置圖片數(shù)據(jù)

配置對象

  • viewMode 視圖控制
    • 0 無限制
    • 1 限制裁剪框不能超出圖片的范圍
    • 2 限制裁剪框不能超出圖片的范圍 且圖片填充模式為 cover 最長邊填充
    • 3 限制裁剪框不能超出圖片的范圍 且圖片填充模式為 contain 最短邊填充
  • dragMode 拖拽圖片模式
    • crop 形成新的裁剪框
    • move 圖片可移動
    • none 什么也沒有
  • initialAspectRatio 裁剪框?qū)捀弑鹊某跏贾?默認與圖片寬高比相同 只有在aspectRatio沒有設(shè)置的情況下可用
  • aspectRatio 設(shè)置裁剪框為固定的寬高比
  • data 之前存儲的裁剪后的數(shù)據(jù) 在初始化時會自動設(shè)置 只有在autoCrop設(shè)置為true時可用
  • preview 預覽 設(shè)置一個區(qū)域容器預覽裁剪后的結(jié)果
    • Element, Array (elements), NodeList or String (selector)
  • responsive 在窗口尺寸調(diào)整后 進行響應(yīng)式的重渲染 默認true
  • restore 在窗口尺寸調(diào)整后 恢復被裁剪的區(qū)域 默認true
  • checkCrossOrigin 檢查圖片是否跨域 默認true 如果是 會在被復制的圖片元素上加上屬性crossOrigin 并且在src上加上一個時間戳 避免重加載圖片時因為瀏覽器緩存而加載錯誤
  • checkOrientation 檢查圖片的方向信息(僅JPEG圖片有)默認true 在旋轉(zhuǎn)圖片時會對圖片方向值做一些處理 以解決IOS設(shè)備上的一些問題
  • modal 是否顯示圖片和裁剪框之間的黑色蒙版 默認true
  • guides 是否顯示裁剪框的虛線 默認true
  • center 是否顯示裁剪框中間的 ‘+’ 指示器 默認true
  • highlight 是否顯示裁剪框上面的白色蒙版 (很淡)默認true
  • background 是否在容器內(nèi)顯示網(wǎng)格狀的背景 默認true
  • autoCrop 允許初始化時自動的裁剪圖片 配合 data 使用 默認true
  • autoCropArea 設(shè)置裁剪區(qū)域占圖片的大小 值為 0-1 默認 0.8 表示 80%的區(qū)域
  • movable 是否可以移動圖片 默認true
  • rotatable 是否可以旋轉(zhuǎn)圖片 默認true
  • scalable 是否可以縮放圖片(可以改變長寬) 默認true
  • zoomable 是否可以縮放圖片(改變焦距) 默認true
  • zoomOnTouch 是否可以通過拖拽觸摸縮放圖片 默認true
  • zoomOnWheel 是否可以通過鼠標滾輪縮放圖片 默認true
  • wheelZoomRatio 設(shè)置鼠標滾輪縮放的靈敏度 默認 0.1
  • cropBoxMovable 是否可以拖拽裁剪框 默認true
  • cropBoxResizable 是否可以改變裁剪框的尺寸 默認true
  • toggleDragModeOnDblclick 是否可以通過雙擊切換拖拽圖片模式(move和crop)默認true 當拖拽圖片模式為none時不可切換 該設(shè)置必須瀏覽器支持雙擊事件
  • minContainerWidth(200)、minContainerHeight(100)、minCanvasWidth(0)、minCanvasHeight(0)、minCropBoxWidth(0)、minCropBoxHeight(0) 容器、圖片、裁剪框的 最小寬高 括號內(nèi)為默認值 注意 裁剪框的最小高寬是相對與頁面而言的 并非相對圖片

方法

  • crop() 手動顯示裁剪框
  • reset() 重置圖片和裁剪框為初始狀態(tài)
  • replace(url[, hasSameSize]) 替換圖片路徑并且重建裁剪框
    • url 新路徑
    • hasSameSize 默認值false 設(shè)置為true表示新老圖片尺寸一樣 只需要更換路徑無需重建裁剪框
  • enable() 解凍 裁剪框
  • disable() 凍結(jié) 裁剪框
  • destroy() 摧毀裁剪框并且移除cropper實例
  • move(offsetX[, offsetY]) 移動圖片指定距離 一個參數(shù)代表橫縱向移動距離一樣
  • moveTo(x[, y]) 移動圖片到一個指定的點 一個參數(shù)代表橫縱向移動距離一樣
  • zoom(ratio) 縮放 ratio大于零是放大 小于零縮小
  • zoomTo(ratio[, pivot]) 縮放并設(shè)置中心點的位置
  • rotate(degree) 旋轉(zhuǎn) 類似css
  • rotateTo(degree) 旋轉(zhuǎn)到絕對角度
  • scale(scaleX[, scaleY])、scaleX(scaleX)、scaleY(scaleY) 縮放 一個參數(shù)代表橫縱向縮放值一樣
  • getData([rounded]) 返回裁剪區(qū)域基于原圖片!原尺寸!的位置和尺寸 rounded默認為false 表示是否顯示四舍五入后的數(shù)據(jù) 有了這些數(shù)據(jù)可以直接在原圖上進行裁剪顯示
  • setData(data) 改變裁剪區(qū)域基于原圖的位置和尺寸 僅當viewMode 不為0時有效
  • getContainerData()、getImageData()、getCanvasData()、setCanvasData(data)、getCropBoxData()、setCropBoxData(data) 容器、圖片容器(畫布)、圖片、裁剪區(qū)域相對容器的數(shù)據(jù)設(shè)置和獲取
  • getCroppedCanvas([options]) 得到被裁剪圖片的一個canvas對象 options設(shè)置這個canvas的一些數(shù)據(jù)
    • width、height、minWidth、minHeight、maxWidth、maxHeight、fillColor、imageSmoothingEnabled (圖片是否是光滑的 默認true)、imageSmoothingQuality(圖片的質(zhì)量 默認low 還有medium、high)
  • setAspectRatio(aspectRatio) 改變裁剪區(qū)域的寬高比
  • setDragMode([mode]) 設(shè)置拖拽圖片模式

事件

  • ready 渲染前(圖片已經(jīng)被加載、cropper實例已經(jīng)準備完畢)的準備工作事件
  • cropstart、cropmove、cropend、crop 開始畫裁剪框(或畫布)、畫裁剪框(或畫布)的中途、裁剪框(或畫布)畫完、進行裁剪事件 event.detail.originalEvent、event.detail.action
    • 當autoCrop為true crop事件會在ready之前觸發(fā)
  • zoom 裁剪框縮放事件

本文參考于https://www.cnblogs.com/eightFlying/p/cropper-demo.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容