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


Posted in Javascript onMarch 01, 2018

前端工作中,经常需要图片裁剪的场景,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,实现裁图组件的封装,先写到这啦,如果对你有帮助,还请点个赞噢!

Javascript 相关文章推荐
最常用的12种设计模式小结
Aug 09 Javascript
jQuery获取浏览器中的分辨率实现代码
Apr 23 Javascript
js小数运算出现多位小数如何解决
Oct 08 Javascript
JavaScript中对DOM节点的访问、创建、修改、删除
Nov 16 Javascript
jQuery使用$.ajax进行即时验证实例详解
Dec 11 Javascript
jQuery鼠标悬浮链接弹出跟随图片实例代码
Jan 08 Javascript
jQuery实现查找最近父节点的方法
Jun 23 Javascript
Angular 应用技巧总结
Sep 14 Javascript
AngularJs 动态加载模块和依赖
Sep 15 Javascript
js屏蔽退格键(backspace或者叫后退键与F5)
Feb 10 Javascript
vue-cli 3.x配置跨域代理的实现方法
Apr 12 Javascript
vue 使用lodash实现对象数组深拷贝操作
Sep 10 Javascript
vue.js 实现图片本地预览 裁剪 压缩 上传功能
Mar 01 #Javascript
vue中使用cropperjs的方法
Mar 01 #Javascript
cropper js基于vue的图片裁剪上传功能的实现代码
Mar 01 #Javascript
Vuex中mutations与actions的区别详解
Mar 01 #Javascript
vue 实现剪裁图片并上传服务器功能
Mar 01 #Javascript
解决easyui日期时间框ie的兼容的问题
Mar 01 #Javascript
详解Immutable及 React 中实践
Mar 01 #Javascript
You might like
浅谈PHP强制类型转换,慎用!
2013/06/06 PHP
5种PHP创建数组的实例代码分享
2014/01/17 PHP
php堆排序实现原理与应用方法
2015/01/03 PHP
jQuery+Ajax+PHP“喜欢”评级功能实现代码
2015/10/08 PHP
php中注册器模式类用法实例分析
2015/11/03 PHP
python进程与线程小结实例分析
2018/11/11 PHP
JavaScript中令你抓狂的魔术变量
2006/11/30 Javascript
需要做特殊处理的DOM元素属性的访问
2010/11/05 Javascript
nodejs实现的一个简单聊天室功能分享
2014/12/06 NodeJs
JavaScript 模块化编程(笔记)
2015/04/08 Javascript
JavaScript资源预加载组件和滑屏组件的使用推荐
2016/03/10 Javascript
移动端滑动插件Swipe教程
2016/10/16 Javascript
深入理解vue.js中的v-if和v-show
2017/06/22 Javascript
JS基于贪心算法解决背包问题示例
2017/11/27 Javascript
Vue项目中使用better-scroll实现一个轮播图自动播放功能
2018/12/03 Javascript
vue-router beforeEach跳转路由验证用户登录状态
2018/12/26 Javascript
vue-form表单验证是否为空值的实例详解
2019/10/29 Javascript
Object.keys() 和 Object.getOwnPropertyNames() 的区别详解
2020/05/21 Javascript
基于jquery实现彩色投票进度条代码解析
2020/08/26 jQuery
[02:32]DOTA2英雄基础教程 美杜莎
2014/01/07 DOTA
[08:54]DOTA2-DPC中国联赛 正赛 Aster vs LBZS 选手采访
2021/03/11 DOTA
Python文件和目录操作详解
2015/02/08 Python
Python 自动化表单提交实例代码
2017/06/08 Python
Python2和Python3中@abstractmethod使用方法
2020/02/04 Python
python pprint模块中print()和pprint()两者的区别
2020/02/10 Python
Python 代码调试技巧示例代码
2020/08/11 Python
Foot Locker英国官网:美国知名运动产品零售商
2019/02/21 全球购物
专注澳大利亚特产和新西兰特产的澳洲中文网:0061澳洲制造
2019/03/24 全球购物
法院实习人员自我鉴定
2013/09/26 职场文书
药学专业个人的自我评价
2013/12/31 职场文书
网上开商店的创业计划书
2014/01/19 职场文书
学校2014重阳节活动策划方案
2014/09/16 职场文书
体育教师个人工作总结
2015/02/09 职场文书
关于开学的感想
2015/08/10 职场文书
什么是创业计划书?什么是商业计划书?这里一一解答
2019/07/12 职场文书
CSS 一行代码实现头像与国旗的融合
2021/10/24 HTML / CSS