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 相关文章推荐
Ajax一统天下之Dojo整合篇
Mar 24 Javascript
jquery 年会抽奖程序
Dec 22 Javascript
关于JavaScript中name的意义冲突示例介绍
May 29 Javascript
JQuery对表单元素的基本操作使用总结
Jul 18 Javascript
JavaScript跨平台的开源框架NativeScript
Mar 24 Javascript
微信小程序 PHP生成带参数二维码
Feb 21 Javascript
Bootstrap面板(Panels)的简单实现代码
Mar 17 Javascript
vue2 router 动态传参,多个参数的实例
Nov 10 Javascript
微信小程序自定义toast实现方法详解【附demo源码下载】
Nov 28 Javascript
jquery点击回车键实现登录效果并默认焦点的方法
Mar 09 jQuery
React注册倒计时功能的实现
Sep 06 Javascript
详解JS实现简单的时分秒倒计时代码
Apr 25 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数组 为文章加关键字连接 文章内容自动加链接
2011/12/29 PHP
PHP实现蛇形矩阵,回环矩阵及数字螺旋矩阵的方法分析
2017/05/29 PHP
PHP设计模式之PHP迭代器模式讲解
2019/03/22 PHP
分享别人写的一个小型js框架
2007/08/13 Javascript
JS 用6N±1法求素数 实例教程
2009/10/20 Javascript
JavaScript格式化数字的函数代码
2010/11/30 Javascript
Javascript 闭包引起的IE内存泄露分析
2012/05/23 Javascript
基于jquery实现控制经纬度显示地图与卫星
2013/05/20 Javascript
JavaScript中反正弦函数Math.asin()的使用简介
2015/06/14 Javascript
利用HTML5的画布Canvas实现刮刮卡效果
2015/09/06 Javascript
深入浅析JavaScript系列(13):This? Yes,this!
2016/01/05 Javascript
Angular 2父子组件数据传递之@Input和@Output详解 (上)
2017/07/05 Javascript
JS倒计时实例_天时分秒
2017/08/22 Javascript
详解vue-cli 构建Vue项目遇到的坑
2017/08/30 Javascript
vue2.0组件之间传值、通信的多种方式(干货)
2018/02/10 Javascript
vue实现pdf导出解决生成canvas模糊等问题(推荐)
2018/10/18 Javascript
基于js Canvas实现二次贝塞尔曲线
2018/12/25 Javascript
es6函数之rest参数用法实例分析
2020/04/18 Javascript
vue修改Element的el-table样式的4种方法
2020/09/17 Javascript
python使用range函数计算一组数和的方法
2015/05/07 Python
Python的Django框架中的表单处理示例
2015/07/17 Python
详解K-means算法在Python中的实现
2017/12/05 Python
Python 函数返回值的示例代码
2019/03/11 Python
利用python+request通过接口实现人员通行记录上传功能
2021/01/13 Python
一款基于css3麻将筛子3D翻转特效的实例教程
2014/12/31 HTML / CSS
网站性能延迟加载图像的五种技巧(小结)
2020/08/13 HTML / CSS
道德模范先进事迹
2014/02/14 职场文书
会计电算化专业自荐信
2014/03/15 职场文书
合伙协议书
2014/04/23 职场文书
领导干部群众路线对照检查材料
2014/11/05 职场文书
2015世界地球日活动总结
2015/02/09 职场文书
运动员代表致辞
2015/07/29 职场文书
导游词之吉林花园山
2019/10/17 职场文书
python学习之panda数据分析核心支持库
2021/05/07 Python
React如何创建组件
2021/06/27 Javascript
nginx刷新页面出现404解决方案(亲测有效)
2022/03/18 Servers