This article mainly introduces vue-cli combined with Element-ui based on cropper.js to encapsulate vue to implement the image cropping component function. This article introduces it to you in great detail with pictures and texts. Friends who need it can refer to it
Front end At work, image cropping is often required. cropper.js
is an excellent front-end plug-in with a very rich API.
This article encapsulates the image cropping plug-in under the vue-cli project. The rendering is as follows:
Without further ado, let’s look at the steps.
Step one: Prepare the development environment
cropper.js is based on jquery, so jquery must be installed first
Execute command:
npm install --save-dev jquery cropper
Add jquery mapping to webpack configuration
Modifywebpack.base.conf.js
Configuration, add a line marked in red
Step 2: Create a new image cropping component
index.vue content:
Due to the use of element-ui, the layout refers to the components of element-ui
template:
<template> <p class="modal-dialog modal-lg" :id="id"> <p class="modal-content"> <form class="avatar-form" enctype="multipart/form-data" method="post"> <p class="modal-header"> </p> <p class="modal-body"> <p class="avatar-body"> <!-- Upload image and data --> <p 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"> </p> <!-- Crop and preview --> <el-row> <el-col :span="18"> <p class="avatar-wrapper"></p> </el-col> <el-col :span="6" style="overflow: hidden;"> <p style="padding-left: 10px"> <p class="avatar-preview preview-lg" ></p> <p class="avatar-preview avatar-preview-round preview-md"></p> <!--<p class="avatar-preview preview-sm"></p>--> </p> </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> </p> </p> </form> </p> </p> </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>
The third step: the parent component refers to the child component
The el-dialog component in element-ui is used, and the el-dialog component is the parent component at this time
Introducing sub-components into parent components
import cropper from '@/components/Cropper/index'
template:
<template> <p 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> </p>
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 } } }
The above is what I compiled for everyone, I hope it will be useful to everyone in the future helpful.
Related articles:
Solve the problem of Vue modifying the array through the following table and the page not rendering
Vue.js realizes the randomness of images Drag method
JS string example to remove consecutive or all repeated characters
The above is the detailed content of Use vue-cli combined with Element-ui to encapsulate vue based on cropper.js to implement the image cropping component function. For more information, please follow other related articles on the PHP Chinese website!