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

vue-cli结合Element-ui基于cropper.js封装vue实现图片裁剪组件功能

程序员文章站 2023-08-02 22:13:34
前端工作中,经常需要图片裁剪的场景,cropper.js是一款优秀的前端插件,api十分丰富。 本文是在vue-cli项目下封装图片裁剪插件,效果图如下: 话不多说...

前端工作中,经常需要图片裁剪的场景,cropper.js是一款优秀的前端插件,api十分丰富。

本文是在vue-cli项目下封装图片裁剪插件,效果图如下:

vue-cli结合Element-ui基于cropper.js封装vue实现图片裁剪组件功能

话不多说,看步骤吧。

第一步:准备开发环境

cropper.js是基于jquery的,所以要先安装jquery

执行命令:

  npm  install --save-dev jquery cropper

 为webpack配置添加jquery的映射

修改webpack.base.conf.js配置,添加标红的一行

vue-cli结合Element-ui基于cropper.js封装vue实现图片裁剪组件功能

第二步:新建图片裁剪组件

vue-cli结合Element-ui基于cropper.js封装vue实现图片裁剪组件功能

index.vue内容:

由于用了element-ui,其中布局就引用了element-ui的组件

 template:

<template>
 <div class="modal-dialog modal-lg" :id="id">
 <div class="modal-content">
  <form class="avatar-form" enctype="multipart/form-data" method="post">
  <div class="modal-header">
  </div>
  <div class="modal-body">
   <div class="avatar-body">
   <!-- upload image and data -->
   <div class="avatar-upload">
    <input type="hidden" class="avatar-src" name="avatar_src">
    <input type="hidden" class="avatar-data" name="ci">
    <label for="avatarinput" class="el-button el-button--primary">选择图片</label>
    <input type="file" class="avatar-input " style="visibility: hidden" id="avatarinput" name="file">
   </div>
   <!-- crop and preview -->
   <el-row>
    <el-col :span="18">
    <div class="avatar-wrapper"></div>
    </el-col>
    <el-col :span="6" style="overflow: hidden;">
    <div style="padding-left: 10px">
     <div class="avatar-preview preview-lg" ></div>
     <div class="avatar-preview avatar-preview-round preview-md"></div>
    <!--<div class="avatar-preview preview-sm"></div>-->
    </div>
    </el-col>
   </el-row>
   <el-row class="avatar-btns">
    <el-col :span="18">
    <el-button-group>
     <button type="primary" class="el-button el-button--primary" data-method="rotate" data-option="-180" title="rotate -180 degrees">-180deg</button>
     <button type="primary" class="el-button el-button--primary" data-method="rotate" data-option="-90" title="rotate -90 degrees">-90deg</button>
     <button type="primary" class="el-button el-button--primary" data-method="rotate" data-option="-45" title="rotate -45 degrees">-45deg</button>
     <button type="primary" class="el-button el-button--primary" data-method="rotate" data-option="45" title="rotate 45 degrees">45deg</button>
     <button type="primary" class="el-button el-button--primary" data-method="rotate" data-option="90" title="rotate 90 degrees">90deg</button>
     <button type="primary" class="el-button el-button--primary" data-method="rotate" data-option="180" title="rotate 180 degrees">180deg</button>
    </el-button-group>
    </el-col>
    <el-col :span="6"></el-col>
   </el-row>
   <el-row>
    <!--<button type="submit" class="btn btn-primary btn-block avatar-save">裁取</button>-->
   </el-row>
   </div>
  </div>
  </form>
 </div>
 </div>
</template>

style:

<style rel="stylesheet/scss" lang='scss' scoped>
 /*@import "cropper/dist/cropper.css";*/
 /*!
 * cropper v3.1.3
 * https://github.com/fengyuanchen/cropper
 *
 * copyright (c) 2014-2017 chen fengyuan
 * released under the mit license
 *
 * date: 2017-10-21t10:03:37.133z
 */
 .avatar-wrapper{
 width: 100%;
 height: 100%;
 overflow: hidden;
 }
 .cropper-container {
 direction: ltr;
 font-size: 0;
 line-height: 0;
 position: relative;
 -ms-touch-action: none;
 touch-action: none;
 -webkit-user-select: none;
 -moz-user-select: none;
 -ms-user-select: none;
 user-select: none;
 }
 .cropper-container img {/*avoid margin top issue (occur only when margin-top <= -height)
 */
 display: block;
 height: 100%;
 image-orientation: 0deg;
 max-height: none !important;
 max-width: none !important;
 min-height: 0 !important;
 min-width: 0 !important;
 width: 100%;
 }
 .cropper-wrap-box,
 .cropper-canvas,
 .cropper-drag-box,
 .cropper-crop-box,
 .cropper-modal {
 bottom: 0;
 left: 0;
 position: absolute;
 right: 0;
 top: 0;
 }
 .cropper-wrap-box,
 .cropper-canvas {
 overflow: hidden;
 }
 .cropper-drag-box {
 background-color: #fff;
 opacity: 0;
 }
 .cropper-modal {
 background-color: #000;
 opacity: .5;
 }
 .cropper-view-box {
 display: block;
 height: 100%;
 outline-color: rgba(51, 153, 255, 0.75);
 outline: 1px solid #39f;
 overflow: hidden;
 width: 100%;
 }
 .cropper-dashed {
 border: 0 dashed #eee;
 display: block;
 opacity: .5;
 position: absolute;
 }
 .cropper-dashed.dashed-h {
 border-bottom-width: 1px;
 border-top-width: 1px;
 height: 33.33333%;
 left: 0;
 top: 33.33333%;
 width: 100%;
 }
 .cropper-dashed.dashed-v {
 border-left-width: 1px;
 border-right-width: 1px;
 height: 100%;
 left: 33.33333%;
 top: 0;
 width: 33.33333%;
 }
 .cropper-center {
 display: block;
 height: 0;
 left: 50%;
 opacity: .75;
 position: absolute;
 top: 50%;
 width: 0;
 }
 .cropper-center:before,
 .cropper-center:after {
 background-color: #eee;
 content: ' ';
 display: block;
 position: absolute;
 }
 .cropper-center:before {
 height: 1px;
 left: -3px;
 top: 0;
 width: 7px;
 }
 .cropper-center:after {
 height: 7px;
 left: 0;
 top: -3px;
 width: 1px;
 }
 .cropper-face,
 .cropper-line,
 .cropper-point {
 display: block;
 height: 100%;
 opacity: .1;
 position: absolute;
 width: 100%;
 }
 .cropper-face {
 background-color: #fff;
 left: 0;
 top: 0;
 }
 .cropper-line {
 background-color: #39f;
 }
 .cropper-line.line-e {
 cursor: e-resize;
 right: -3px;
 top: 0;
 width: 5px;
 }
 .cropper-line.line-n {
 cursor: n-resize;
 height: 5px;
 left: 0;
 top: -3px;
 }
 .cropper-line.line-w {
 cursor: w-resize;
 left: -3px;
 top: 0;
 width: 5px;
 }
 .cropper-line.line-s {
 bottom: -3px;
 cursor: s-resize;
 height: 5px;
 left: 0;
 }
 .cropper-point {
 background-color: #39f;
 height: 5px;
 opacity: .75;
 width: 5px;
 }
 .cropper-point.point-e {
 cursor: e-resize;
 margin-top: -3px;
 right: -3px;
 top: 50%;
 }
 .cropper-point.point-n {
 cursor: n-resize;
 left: 50%;
 margin-left: -3px;
 top: -3px;
 }
 .cropper-point.point-w {
 cursor: w-resize;
 left: -3px;
 margin-top: -3px;
 top: 50%;
 }
 .cropper-point.point-s {
 bottom: -3px;
 cursor: s-resize;
 left: 50%;
 margin-left: -3px;
 }
 .cropper-point.point-ne {
 cursor: ne-resize;
 right: -3px;
 top: -3px;
 }
 .cropper-point.point-nw {
 cursor: nw-resize;
 left: -3px;
 top: -3px;
 }
 .cropper-point.point-sw {
 bottom: -3px;
 cursor: sw-resize;
 left: -3px;
 }
 .cropper-point.point-se {
 bottom: -3px;
 cursor: se-resize;
 height: 20px;
 opacity: 1;
 right: -3px;
 width: 20px;
 }
 @media (min-width: 768px) {
 .cropper-point.point-se {
  height: 15px;
  width: 15px;
 }
 }
 @media (min-width: 992px) {
 .cropper-point.point-se {
  height: 10px;
  width: 10px;
 }
 }
 @media (min-width: 1200px) {
 .cropper-point.point-se {
  height: 5px;
  opacity: .75;
  width: 5px;
 }
 }
 .cropper-point.point-se:before {
 background-color: #39f;
 bottom: -50%;
 content: ' ';
 display: block;
 height: 200%;
 opacity: 0;
 position: absolute;
 right: -50%;
 width: 200%;
 }
 .cropper-invisible {
 opacity: 0;
 }
 .cropper-bg {
 background-image: url('data:image/png;base64,ivborw0kggoaaaansuheugaaabaaaaaqaqmaaaalpw0iaaaaa3ncsvqicajb4u/gaaaablbmvexmzmz////tjrv2aaaacxbiwxmaaarraaak6wgciw1aaaaahhrfwhrtb2z0d2fyzqbbzg9izsbgaxjld29ya3mgq1m26lyyjaaaabfjrefucjlj+m/agbvhf/0pah6/d/hkdxogaaaaaelftksuqmcc');
 }
 .cropper-hide {
 display: block;
 height: 0;
 position: absolute;
 width: 0;
 }
 .cropper-hidden {
 display: none !important;
 }
 .cropper-move {
 cursor: move;
 }
 .cropper-crop {
 cursor: crosshair;
 }
 .cropper-disabled .cropper-drag-box,
 .cropper-disabled .cropper-face,
 .cropper-disabled .cropper-line,
 .cropper-disabled .cropper-point {
 cursor: not-allowed;
 }
 .avatar-view {
 display: block;
 margin: 15% auto 5%;
 height: 220px;
 width: 220px;
 border: 3px solid #fff;
 border-radius: 5px;
 box-shadow: 0 0 5px rgba(0,0,0,.15);
 cursor: pointer;
 overflow: hidden;
 }
 .avatar-view img {
 width: 100%;
 }
 .avatar-body {
 padding-right: 15px;
 padding-left: 15px;
 }
 .avatar-upload {
 overflow: hidden;
 }
 .avatar-upload label {
 display: block;
 float: left;
 clear: left;
 width: 100px;
 }
 .avatar-upload input {
 display: block;
 margin-left: 110px;
 }
 .avatar-alert {
 margin-top: 10px;
 margin-bottom: 10px;
 }
 .avatar-wrapper {
 height: 364px;
 width: 100%;
 margin-top: 15px;
 box-shadow: inset 0 0 5px rgba(0,0,0,.25);
 background-color: #fcfcfc;
 overflow: hidden;
 }
 .avatar-wrapper img {
 display: block;
 height: auto;
 max-width: 100%;
 }
 .avatar-preview {
 float: left;
 margin-top: 15px;
 margin-right: 15px;
 border: 1px solid #eee;
 border-radius: 4px;
 background-color: #fff;
 overflow: hidden;
 }
 .avatar-preview:hover {
 border-color: #ccf;
 box-shadow: 0 0 5px rgba(0,0,0,.15);
 }
 .avatar-preview img {
 width: 100%;
 }
 .avatar-preview-round{
 border-radius: 50%;
 }
 .preview-lg {
 height: 184px;
 width: 184px;
 margin-top: 15px;
 }
 .preview-md {
 height: 100px;
 width: 100px;
 }
 .preview-sm {
 height: 50px;
 width: 50px;
 }
 @media (min-width: 992px) {
 .avatar-preview {
  float: none;
 }
 }
 .avatar-btns {
 margin-top: 30px;
 margin-bottom: 15px;
 }
 .avatar-btns .btn-group {
 margin-right: 5px;
 }
</style>

script:

<script>
 import $ from 'jquery'
 import 'cropper/dist/cropper.js'
 export default {
 props:{
  id:string
 },
 data(){
  return {
  $container:null,
  $avatarview:null,
  $avatarmodal : null,
  $loading : null,
  $avatarform : null,
  $avatarupload : null,
  $avatarsrc : null,
  $avatardata : null,
  $avatarinput : null,
  $avatarsave: null,
  $avatarbtns : null,
  $avatarwrapper : null,
  $avatarpreview: null,
  support: {
  filelist: !!$('<input type="file">').prop('files'),
   bloburls: !!window.url && url.createobjecturl,
   formdata: !!window.formdata
  }
  }
 },
 created(){},
 mounted(){
  this.$container = $('#'+this.id);
  this.$avatarform = this.$container.find('.avatar-form');
  this.$avatarupload = this.$avatarform.find('.avatar-upload');
  this.$avatarsrc = this.$avatarform.find('.avatar-src');
  this.$avatardata = this.$avatarform.find('.avatar-data');
  this.$avatarinput = this.$avatarform.find('.avatar-input');
  this.$avatarsave = this.$avatarform.find('.avatar-save');
  this.$avatarwrapper = this.$container.find('.avatar-wrapper');
  this.$avatarpreview = this.$container.find('.avatar-preview');
  this.$avatarbtns = this.$container.find('.avatar-btns');
  this.$nexttick(function () {
   this.init();
  })
 },
 methods:{
  init: function () {
  this.support.datauri = this.support.filelist && this.support.bloburls;
  this.addlistener();
//  this.startcropper();
  },
  addlistener: function () {
  this.$avatarinput.on('change', $.proxy(this.change, this));
  this.$avatarform.on('submit', $.proxy(this.submit, this));
  this.$avatarbtns.on('click', $.proxy(this.rotate, this));
  },
  initpreview: function () {
  var url = this.$avatar.attr('src');
  this.$avatarpreview.html('<img src="' + url + '">');
  },
  initiframe: function () {
  var target = 'upload-iframe-' + (new date()).gettime();
  var $iframe = $('<iframe>').attr({
   name: target,
   src: ''
  });
  var _this = this;
  // ready ifrmae
  $iframe.one('load', function () {
   // respond response
   $iframe.on('load', function () {
   var data;
   try {
    data = $(this).contents().find('body').text();
   } catch (e) {
    console.log(e.message);
   }
   if (data) {
    try {
    data = $.parsejson(data);
    } catch (e) {
    console.log(e.message);
    }
    _this.submitdone(data);
   } else {
   }
   _this.submitend();
   });
  });
  this.$iframe = $iframe;
  this.$avatarform.attr('target', target).after($iframe.hide());
  },
  click:function () {
  this.initpreview();
  },
  change: function () {
  var files;
  var file;
  if (this.support.datauri) {
   files = this.$avatarinput.prop('files');
   if (files.length > 0) {
   file = files[0];
   if (this.isimagefile(file)) {
    if (this.url) {
    url.revokeobjecturl(this.url); // revoke the old one
    }
    this.url = url.createobjecturl(file);
    this.startcropper();
   }
   }
  } else {
   file = this.$avatarinput.val();
   if (this.isimagefile(file)) {
   this.syncupload();
   }
  }
  },
  //裁剪提交
  submit: function () {
  if (!this.$avatarsrc.val() && !this.$avatarinput.val()) {
   return false;
  }
  if (this.support.formdata) {
   this.ajaxupload();
   return false;
  }
  },  //旋转事件
  rotate: function (e) {
  var data;
  if (this.active) {
   data = $(e.target).data();
   if (data.method) {
   this.$img.cropper(data.method, data.option);
   }
  }
  },
  isimagefile: function (file) {
  if (file.type) {
   return /^image\/\w+$/.test(file.type);
  } else {
   return /\.(jpg|jpeg|png|gif)$/.test(file);
  }
  },
  startcropper: function () {
  var _this = this;
  if (this.active) {
   this.$img.cropper('replace', this.url);
  } else {
   this.$img = $('<img src="' + this.url + '">');
   this.$avatarwrapper.empty().html(this.$img);
   this.$img.cropper({
   viewmode:1,
   aspectratio: 1,
   preview: this.$avatarpreview,
   restore:false,
   crop: function (e) {
    var json = [
    '{"x":' + e.x,
    '"y":' + e.y,
    '"height":' + e.height,
    '"width":' + e.width,
    '"rotate":' + e.rotate + '}'
    ].join();
    //裁图参数存起来
    _this.$avatardata.val(json);
   }
   });
   this.active = true;
  }
  },
  stopcropper: function () {
  if (this.active) {
   this.$img.cropper('destroy');
   this.$img.remove();
   this.active = false;
  }
  },
  ajaxupload: function () {
  var url = '/oss/file/cropping';
  var data = new formdata(this.$avatarform[0]);
  var _this = this;
  $.ajax(url, {
   type: 'post',
   data: data,
   datatype: 'json',
   processdata: false,
   contenttype: false,
   success: function (data,textstatus) {
   _this.submitdone(data);
   if(data.success){
    //将返回的数据传给父组件
    _this.$emit('cropper-success',data.data);
    _this.cropdone();
   }
   },
  });
  },
  syncupload: function () {
  this.$avatarsave.click();
  },
  submitdone: function (data) {
  if ($.isplainobject(data) && data.state === 200) {
   if (data.result) {
   this.url = data.result;
   if (this.support.datauri || this.uploaded) {
    this.uploaded = false;
    this.cropdone();
   } else {
    this.uploaded = true;
    this.$avatarsrc.val(this.url);
    this.startcropper();
   }
   this.$avatarinput.val('');
   } else if (data.message) {
   }
  } else {
  }
  },
  cropdone: function () {
//  this.$avatarform.get(0).reset();
//  this.$avatarsrc.prop('src', this.url);
   this.stopcropper();
//  this.$container.hide();
  }
 }
 }
</script>

第三步:父组件引用子组件

用了element-ui中的 el-dialog组件,此时el-dialog组件为父组件

在父组件中引入子组件

import cropper from '@/components/cropper/index'

template:

<template>
 <div class="app-main-content" >
 <el-dialog :visible.sync="showcropper" title="封面裁图" width="70%">
  <cropper id="avatarcrop" ref="cropper" @cropper-success="croppersuccesshandle"></cropper>
  <span slot="footer" class="dialog-footer">
  <el-button @click="cancelcropper">取 消</el-button>
  <el-button type="primary" @click="tocropper">确 定</el-button>
  </span>
 </el-dialog>
 </div>

script:

import cropper from '@/components/cropper/index'
export default {
 name: 'addnews',
 components:{
  cropper
 },
 data(){
  return {
  avatarurl2: null,
  showcropper:false
  }
 },
 methods:{
  //隐藏裁剪框
  cancelcropper(){
  this.showcropper = false
  this.$refs.cropper.cropdone();
  },
  //父组件调用子组件裁剪方法
  tocropper(){
   this.$refs.cropper.submit();
  },
  //子组件裁剪方法成功执行后与父组件通信
  croppersuccesshandle(data){
   //返回data
  this.showcropper = false
  this.avatarurl2 = data.url
  }
 }
 }

本文结合element-ui,vue-cli,jquery,cropper.js,实现裁图组件的封装,先写到这啦,如果对你有帮助,还请点个赞噢!