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 相关文章推荐
javascript引用对象的方法
Jan 11 Javascript
jQuery 相关控件的事件操作分解
Aug 03 Javascript
加速IE的Javascript document输出的方法
Dec 02 Javascript
JavaScript中使用构造器创建对象无需new的情况说明
Mar 01 Javascript
JavaScript DOM事件(笔记)
Apr 08 Javascript
jQuery实现的Div窗口震动效果实例
Aug 07 Javascript
javascript实现动态统计图开发实例
Nov 21 Javascript
node.js实现登录注册页面
Apr 08 Javascript
详解angularJS+Ionic移动端图片上传的解决办法
Sep 13 Javascript
JavaScript实现随机数生成器(去重)
Oct 13 Javascript
vue-cli3脚手架的配置及使用教程
Aug 28 Javascript
angularjs http与后台交互的实现示例
Dec 21 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调用三种数据库的方法(1)
2006/10/09 PHP
PHP获取网址的顶级域名函数代码
2012/09/24 PHP
Linux系统中设置多版本PHP共存配合Nginx服务器使用
2015/12/21 PHP
Zend Framework动作助手Redirector用法实例详解
2016/03/05 PHP
php 实现简单的登录功能示例【基于thinkPHP框架】
2019/12/02 PHP
input、button的不同type值在ajax提交表单时导致的陷阱
2009/02/24 Javascript
JS 对输入框进行限制(常用的都有)
2013/07/30 Javascript
js无法获取到html标签的属性的解决方法
2016/07/26 Javascript
微信小程序商城项目之购物数量加减(3)
2017/04/17 Javascript
JS实现的JSON数组去重算法示例
2018/04/11 Javascript
r.js来合并压缩css文件的示例
2018/04/26 Javascript
用node开发并发布一个cli工具的方法步骤
2019/01/03 Javascript
微信小程序学习笔记之跳转页面、传递参数获得数据操作图文详解
2019/03/28 Javascript
Vue 指令实现按钮级别权限管理功能
2019/04/23 Javascript
vue-cli3 项目优化之通过 node 自动生成组件模板 generate View、Component
2019/04/30 Javascript
[47:10]完美世界DOTA2联赛PWL S3 LBZS vs Rebirth 第二场 12.16
2020/12/18 DOTA
[54:26]完美世界DOTA2联赛PWL S3 Forest vs Rebirth 第一场 12.10
2020/12/12 DOTA
python模拟登陆Tom邮箱示例分享
2014/01/13 Python
python登录pop3邮件服务器接收邮件的方法
2015/04/30 Python
Python3生成手写体数字方法
2018/01/30 Python
Python 实现12306登录功能实例代码
2018/02/09 Python
利用python库在局域网内传输文件的方法
2018/06/04 Python
Python字符串逆序的实现方法【一题多解】
2019/02/18 Python
python set内置函数的具体使用
2019/07/02 Python
python实现将文件夹内的每张图片批量分割成多张
2019/07/22 Python
python中sympy库求常微分方程的用法
2020/04/28 Python
基于logstash实现日志文件同步elasticsearch
2020/08/06 Python
HTML5 video 视频标签使用介绍
2014/02/03 HTML / CSS
印尼穆斯林时尚购物网站:Hijabenka
2016/12/10 全球购物
捷克汽车配件和工具销售网站:TorriaCars
2018/02/26 全球购物
幼儿园秋游感想
2014/03/12 职场文书
幼儿园小班评语
2014/04/18 职场文书
2014矛盾纠纷排查调处工作总结
2014/12/09 职场文书
四年级小学生评语
2014/12/26 职场文书
Python还能这么玩之只用30行代码从excel提取个人值班表
2021/06/05 Python
python数字类型和占位符详情
2022/03/13 Python