<template> <div v-show="value" class="vue-image-crop-upload"> <div class="vicp-wrap"> <div class="vicp-close" @click="off"> <i class="vicp-icon4" /> </div> <div v-show="step == 1" class="vicp-step1"> <div class="vicp-drop-area" @dragleave="preventDefault" @dragover="preventDefault" @dragenter="preventDefault" @click="handleClick" @drop="handleChange" > <i v-show="loading != 1" class="vicp-icon1"> <i class="vicp-icon1-arrow" /> <i class="vicp-icon1-body" /> <i class="vicp-icon1-bottom" /> </i> <span v-show="loading !== 1" class="vicp-hint">{{ lang.hint }}</span> <span v-show="!isSupported" class="vicp-no-supported-hint">{{ lang.noSupported }}</span> <input v-show="false" v-if="step == 1" ref="fileinput" type="file" @change="handleChange"> </div> <div v-show="hasError" class="vicp-error"> <i class="vicp-icon2" /> {{ errorMsg }} </div> <div class="vicp-operate"> <a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a> </div> </div> <div v-if="step == 2" class="vicp-step2"> <div class="vicp-crop"> <div v-show="true" class="vicp-crop-left"> <div class="vicp-img-container"> <img ref="img" :src="sourceImgUrl" :style="sourceImgStyle" class="vicp-img" draggable="false" @drag="preventDefault" @dragstart="preventDefault" @dragend="preventDefault" @dragleave="preventDefault" @dragover="preventDefault" @dragenter="preventDefault" @drop="preventDefault" @touchstart="imgStartMove" @touchmove="imgMove" @touchend="createImg" @touchcancel="createImg" @mousedown="imgStartMove" @mousemove="imgMove" @mouseup="createImg" @mouseout="createImg" > <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-1" /> <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-2" /> </div> <div class="vicp-range"> <input :value="scale.range" type="range" step="1" min="0" max="100" @input="zoomChange" > <i class="vicp-icon5" @mousedown="startZoomSub" @mouseout="endZoomSub" @mouseup="endZoomSub" /> <i class="vicp-icon6" @mousedown="startZoomAdd" @mouseout="endZoomAdd" @mouseup="endZoomAdd" /> </div> <div v-if="!noRotate" class="vicp-rotate"> <i @mousedown="startRotateLeft" @mouseout="endRotate" @mouseup="endRotate">↺</i> <i @mousedown="startRotateRight" @mouseout="endRotate" @mouseup="endRotate">↻</i> </div> </div> <div v-show="true" class="vicp-crop-right"> <div class="vicp-preview"> <div v-if="!noSquare" class="vicp-preview-item"> <img :src="createImgUrl" :style="previewStyle"> <span>{{ lang.preview }}</span> </div> <div v-if="!noCircle" class="vicp-preview-item vicp-preview-item-circle"> <img :src="createImgUrl" :style="previewStyle"> <span>{{ lang.preview }}</span> </div> </div> </div> </div> <div class="vicp-operate"> <a @click="setStep(1)" @mousedown="ripple">{{ lang.btn.back }}</a> <a class="vicp-operate-btn" @click="prepareUpload" @mousedown="ripple">{{ lang.btn.save }}</a> </div> </div> <div v-if="step == 3" class="vicp-step3"> <div class="vicp-upload"> <span v-show="loading === 1" class="vicp-loading">{{ lang.loading }}</span> <div class="vicp-progress-wrap"> <span v-show="loading === 1" :style="progressStyle" class="vicp-progress" /> </div> <div v-show="hasError" class="vicp-error"> <i class="vicp-icon2" /> {{ errorMsg }} </div> <div v-show="loading === 2" class="vicp-success"> <i class="vicp-icon3" /> {{ lang.success }} </div> </div> <div class="vicp-operate"> <a @click="setStep(2)" @mousedown="ripple">{{ lang.btn.back }}</a> <a @click="off" @mousedown="ripple">{{ lang.btn.close }}</a> </div> </div> <canvas v-show="false" ref="canvas" :width="width" :height="height" /> </div> </div> </template> <script> 'use strict' import request from '@/utils/request' import language from './utils/language.js' import mimes from './utils/mimes.js' import data2blob from './utils/data2blob.js' import effectRipple from './utils/effectRipple.js' export default { props: { // 域,上传文件name,触发事件会带上(如果一个页面多个图片上传控件,可以做区分 field: { type: String, default: 'avatar' }, // 原名key,类似于id,触发事件会带上(如果一个页面多个图片上传控件,可以做区分 ki: { type: Number, default: 0 }, // 显示该控件与否 value: { type: Boolean, default: true }, // 上传地址 url: { type: String, default: '' }, // 其他要上传文件附带的数据,对象格式 params: { type: Object, default: null }, // Add custom headers headers: { type: Object, default: null }, // 剪裁图片的宽 width: { type: Number, default: 200 }, // 剪裁图片的高 height: { type: Number, default: 200 }, // 不显示旋转功能 noRotate: { type: Boolean, default: true }, // 不预览圆形图片 noCircle: { type: Boolean, default: false }, // 不预览方形图片 noSquare: { type: Boolean, default: false }, // 单文件大小限制 maxSize: { type: Number, default: 10240 }, // 语言类型 langType: { type: String, default: 'zh' }, // 语言包 langExt: { type: Object, default: null }, // 图片上传格式 imgFormat: { type: String, default: 'png' }, // 是否支持跨域 withCredentials: { type: Boolean, default: false } }, data() { const { imgFormat, langType, langExt, width, height } = this let isSupported = true const allowImgFormat = ['jpg', 'png'] const tempImgFormat = allowImgFormat.indexOf(imgFormat) === -1 ? 'jpg' : imgFormat const lang = language[langType] ? language[langType] : language['en'] const mime = mimes[tempImgFormat] // 规范图片格式 this.imgFormat = tempImgFormat if (langExt) { Object.assign(lang, langExt) } if (typeof FormData !== 'function') { isSupported = false } return { // 图片的mime mime, // 语言包 lang, // 浏览器是否支持该控件 isSupported, // 浏览器是否支持触屏事件 isSupportTouch: document.hasOwnProperty('ontouchstart'), // 步骤 step: 1, // 1选择文件 2剪裁 3上传 // 上传状态及进度 loading: 0, // 0未开始 1正在 2成功 3错误 progress: 0, // 是否有错误及错误信息 hasError: false, errorMsg: '', // 需求图宽高比 ratio: width / height, // 原图地址、生成图片地址 sourceImg: null, sourceImgUrl: '', createImgUrl: '', // 原图片拖动事件初始值 sourceImgMouseDown: { on: false, mX: 0, // 鼠标按下的坐标 mY: 0, x: 0, // scale原图坐标 y: 0 }, // 生成图片预览的容器大小 previewContainer: { width: 100, height: 100 }, // 原图容器宽高 sourceImgContainer: { // sic width: 240, height: 184 // 如果生成图比例与此一致会出现bug,先改成特殊的格式吧,哈哈哈 }, // 原图展示属性 scale: { zoomAddOn: false, // 按钮缩放事件开启 zoomSubOn: false, // 按钮缩放事件开启 range: 1, // 最大100 rotateLeft: false, // 按钮向左旋转事件开启 rotateRight: false, // 按钮向右旋转事件开启 degree: 0, // 旋转度数 x: 0, y: 0, width: 0, height: 0, maxWidth: 0, maxHeight: 0, minWidth: 0, // 最宽 minHeight: 0, naturalWidth: 0, // 原宽 naturalHeight: 0 } } }, computed: { // 进度条样式 progressStyle() { const { progress } = this return { width: progress + '%' } }, // 原图样式 sourceImgStyle() { const { scale, sourceImgMasking } = this const top = scale.y + sourceImgMasking.y + 'px' const left = scale.x + sourceImgMasking.x + 'px' return { top, left, width: scale.width + 'px', height: scale.height + 'px', transform: 'rotate(' + scale.degree + 'deg)', // 旋转时 左侧原始图旋转样式 '-ms-transform': 'rotate(' + scale.degree + 'deg)', // 兼容IE9 '-moz-transform': 'rotate(' + scale.degree + 'deg)', // 兼容FireFox '-webkit-transform': 'rotate(' + scale.degree + 'deg)', // 兼容Safari 和 chrome '-o-transform': 'rotate(' + scale.degree + 'deg)' // 兼容 Opera } }, // 原图蒙版属性 sourceImgMasking() { const { width, height, ratio, sourceImgContainer } = this const sic = sourceImgContainer const sicRatio = sic.width / sic.height // 原图容器宽高比 let x = 0 let y = 0 let w = sic.width let h = sic.height let scale = 1 if (ratio < sicRatio) { scale = sic.height / height w = sic.height * ratio x = (sic.width - w) / 2 } if (ratio > sicRatio) { scale = sic.width / width h = sic.width / ratio y = (sic.height - h) / 2 } return { scale, // 蒙版相对需求宽高的缩放 x, y, width: w, height: h } }, // 原图遮罩样式 sourceImgShadeStyle() { const { sourceImgMasking, sourceImgContainer } = this const sic = sourceImgContainer const sim = sourceImgMasking const w = sim.width === sic.width ? sim.width : (sic.width - sim.width) / 2 const h = sim.height === sic.height ? sim.height : (sic.height - sim.height) / 2 return { width: w + 'px', height: h + 'px' } }, previewStyle() { const { ratio, previewContainer } = this const pc = previewContainer let w = pc.width let h = pc.height const pcRatio = w / h if (ratio < pcRatio) { w = pc.height * ratio } if (ratio > pcRatio) { h = pc.width / ratio } return { width: w + 'px', height: h + 'px' } } }, watch: { value(newValue) { if (newValue && this.loading !== 1) { this.reset() } } }, created() { // 绑定按键esc隐藏此插件事件 document.addEventListener('keyup', this.closeHandler) }, destroyed() { document.removeEventListener('keyup', this.closeHandler) }, methods: { // 点击波纹效果 ripple(e) { effectRipple(e) }, // 关闭控件 off() { setTimeout(() => { this.$emit('input', false) this.$emit('close') if (this.step === 3 && this.loading === 2) { this.setStep(1) } }, 200) }, // 设置步骤 setStep(no) { // 延时是为了显示动画效果呢,哈哈哈 setTimeout(() => { this.step = no }, 200) }, /* 图片选择区域函数绑定 ---------------------------------------------------------------*/ preventDefault(e) { e.preventDefault() return false }, handleClick(e) { if (this.loading !== 1) { if (e.target !== this.$refs.fileinput) { e.preventDefault() if (document.activeElement !== this.$refs) { this.$refs.fileinput.click() } } } }, handleChange(e) { e.preventDefault() if (this.loading !== 1) { const files = e.target.files || e.dataTransfer.files this.reset() if (this.checkFile(files[0])) { this.setSourceImg(files[0]) } } }, /* ---------------------------------------------------------------*/ // 检测选择的文件是否合适 checkFile(file) { const { lang, maxSize } = this // 仅限图片 if (file.type.indexOf('image') === -1) { this.hasError = true this.errorMsg = lang.error.onlyImg return false } // 超出大小 if (file.size / 1024 > maxSize) { this.hasError = true this.errorMsg = lang.error.outOfSize + maxSize + 'kb' return false } return true }, // 重置控件 reset() { this.loading = 0 this.hasError = false this.errorMsg = '' this.progress = 0 }, // 设置图片源 setSourceImg(file) { const fr = new FileReader() fr.onload = e => { this.sourceImgUrl = fr.result this.startCrop() } fr.readAsDataURL(file) }, // 剪裁前准备工作 startCrop() { const { width, height, ratio, scale, sourceImgUrl, sourceImgMasking, lang } = this const sim = sourceImgMasking const img = new Image() img.src = sourceImgUrl img.onload = () => { const nWidth = img.naturalWidth const nHeight = img.naturalHeight const nRatio = nWidth / nHeight let w = sim.width let h = sim.height let x = 0 let y = 0 // 图片像素不达标 if (nWidth < width || nHeight < height) { this.hasError = true this.errorMsg = lang.error.lowestPx + width + '*' + height return false } if (ratio > nRatio) { h = w / nRatio y = (sim.height - h) / 2 } if (ratio < nRatio) { w = h * nRatio x = (sim.width - w) / 2 } scale.range = 0 scale.x = x scale.y = y scale.width = w scale.height = h scale.degree = 0 scale.minWidth = w scale.minHeight = h scale.maxWidth = nWidth * sim.scale scale.maxHeight = nHeight * sim.scale scale.naturalWidth = nWidth scale.naturalHeight = nHeight this.sourceImg = img this.createImg() this.setStep(2) } }, // 鼠标按下图片准备移动 imgStartMove(e) { e.preventDefault() // 支持触摸事件,则鼠标事件无效 if (this.isSupportTouch && !e.targetTouches) { return false } const et = e.targetTouches ? e.targetTouches[0] : e const { sourceImgMouseDown, scale } = this const simd = sourceImgMouseDown simd.mX = et.screenX simd.mY = et.screenY simd.x = scale.x simd.y = scale.y simd.on = true }, // 鼠标按下状态下移动,图片移动 imgMove(e) { e.preventDefault() // 支持触摸事件,则鼠标事件无效 if (this.isSupportTouch && !e.targetTouches) { return false } const et = e.targetTouches ? e.targetTouches[0] : e const { sourceImgMouseDown: { on, mX, mY, x, y }, scale, sourceImgMasking } = this const sim = sourceImgMasking const nX = et.screenX const nY = et.screenY const dX = nX - mX const dY = nY - mY let rX = x + dX let rY = y + dY if (!on) return if (rX > 0) { rX = 0 } if (rY > 0) { rY = 0 } if (rX < sim.width - scale.width) { rX = sim.width - scale.width } if (rY < sim.height - scale.height) { rY = sim.height - scale.height } scale.x = rX scale.y = rY }, // 按钮按下开始向右旋转 startRotateRight(e) { const { scale } = this scale.rotateRight = true const rotate = () => { if (scale.rotateRight) { const degree = ++scale.degree this.createImg(degree) setTimeout(function() { rotate() }, 60) } } rotate() }, // 按钮按下开始向左旋转 startRotateLeft(e) { const { scale } = this scale.rotateLeft = true const rotate = () => { if (scale.rotateLeft) { const degree = --scale.degree this.createImg(degree) setTimeout(function() { rotate() }, 60) } } rotate() }, // 停止旋转 endRotate() { const { scale } = this scale.rotateLeft = false scale.rotateRight = false }, // 按钮按下开始放大 startZoomAdd(e) { const { scale } = this scale.zoomAddOn = true const zoom = () => { if (scale.zoomAddOn) { const range = scale.range >= 100 ? 100 : ++scale.range this.zoomImg(range) setTimeout(function() { zoom() }, 60) } } zoom() }, // 按钮松开或移开取消放大 endZoomAdd(e) { this.scale.zoomAddOn = false }, // 按钮按下开始缩小 startZoomSub(e) { const { scale } = this scale.zoomSubOn = true const zoom = () => { if (scale.zoomSubOn) { const range = scale.range <= 0 ? 0 : --scale.range this.zoomImg(range) setTimeout(function() { zoom() }, 60) } } zoom() }, // 按钮松开或移开取消缩小 endZoomSub(e) { const { scale } = this scale.zoomSubOn = false }, zoomChange(e) { this.zoomImg(e.target.value) }, // 缩放原图 zoomImg(newRange) { const { sourceImgMasking, scale } = this const { maxWidth, maxHeight, minWidth, minHeight, width, height, x, y } = scale const sim = sourceImgMasking // 蒙版宽高 const sWidth = sim.width const sHeight = sim.height // 新宽高 const nWidth = minWidth + ((maxWidth - minWidth) * newRange) / 100 const nHeight = minHeight + ((maxHeight - minHeight) * newRange) / 100 // 新坐标(根据蒙版中心点缩放) let nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x) let nY = sHeight / 2 - (nHeight / height) * (sHeight / 2 - y) // 判断新坐标是否超过蒙版限制 if (nX > 0) { nX = 0 } if (nY > 0) { nY = 0 } if (nX < sWidth - nWidth) { nX = sWidth - nWidth } if (nY < sHeight - nHeight) { nY = sHeight - nHeight } // 赋值处理 scale.x = nX scale.y = nY scale.width = nWidth scale.height = nHeight scale.range = newRange setTimeout(() => { if (scale.range === newRange) { this.createImg() } }, 300) }, // 生成需求图片 createImg(e) { const { mime, sourceImg, scale: { x, y, width, height, degree }, sourceImgMasking: { scale } } = this const canvas = this.$refs.canvas const ctx = canvas.getContext('2d') if (e) { // 取消鼠标按下移动状态 this.sourceImgMouseDown.on = false } canvas.width = this.width canvas.height = this.height ctx.clearRect(0, 0, this.width, this.height) // 将透明区域设置为白色底边 ctx.fillStyle = '#fff' ctx.fillRect(0, 0, this.width, this.height) ctx.translate(this.width * 0.5, this.height * 0.5) ctx.rotate((Math.PI * degree) / 180) ctx.translate(-this.width * 0.5, -this.height * 0.5) ctx.drawImage( sourceImg, x / scale, y / scale, width / scale, height / scale ) this.createImgUrl = canvas.toDataURL(mime) }, prepareUpload() { const { url, createImgUrl, field, ki } = this this.$emit('crop-success', createImgUrl, field, ki) if (typeof url === 'string' && url) { this.upload() } else { this.off() } }, // 上传图片 upload() { const { lang, imgFormat, mime, url, params, field, ki, createImgUrl } = this const fmData = new FormData() fmData.append( field, data2blob(createImgUrl, mime), field + '.' + imgFormat ) // 添加其他参数 if (typeof params === 'object' && params) { Object.keys(params).forEach(k => { fmData.append(k, params[k]) }) } // 监听进度回调 // const uploadProgress = (event) => { // if (event.lengthComputable) { // this.progress = 100 * Math.round(event.loaded) / event.total // } // } // 上传文件 this.reset() this.loading = 1 this.setStep(3) request({ url, method: 'post', data: fmData }) .then(resData => { this.loading = 2 this.$emit('crop-upload-success', resData.data) }) .catch(err => { if (this.value) { this.loading = 3 this.hasError = true this.errorMsg = lang.fail this.$emit('crop-upload-fail', err, field, ki) } }) }, closeHandler(e) { if (this.value && (e.key === 'Escape' || e.keyCode === 27)) { this.off() } } } } </script> <style lang="scss"> @charset "UTF-8"; @-webkit-keyframes vicp_progress { 0% { background-position-y: 0; } 100% { background-position-y: 40px; } } @keyframes vicp_progress { 0% { background-position-y: 0; } 100% { background-position-y: 40px; } } @-webkit-keyframes vicp { 0% { opacity: 0; -webkit-transform: scale(0) translatey(-60px); transform: scale(0) translatey(-60px); } 100% { opacity: 1; -webkit-transform: scale(1) translatey(0); transform: scale(1) translatey(0); } } @keyframes vicp { 0% { opacity: 0; -webkit-transform: scale(0) translatey(-60px); transform: scale(0) translatey(-60px); } 100% { opacity: 1; -webkit-transform: scale(1) translatey(0); transform: scale(1) translatey(0); } } .vue-image-crop-upload { position: fixed; display: block; -webkit-box-sizing: border-box; box-sizing: border-box; z-index: 10000; top: 0; bottom: 0; left: 0; right: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.65); -webkit-tap-highlight-color: transparent; -moz-tap-highlight-color: transparent; } .vue-image-crop-upload .vicp-wrap { -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); position: fixed; display: block; -webkit-box-sizing: border-box; box-sizing: border-box; z-index: 10000; top: 0; bottom: 0; left: 0; right: 0; margin: auto; width: 600px; height: 330px; padding: 25px; background-color: #fff; border-radius: 2px; -webkit-animation: vicp 0.12s ease-in; animation: vicp 0.12s ease-in; } .vue-image-crop-upload .vicp-wrap .vicp-close { position: absolute; right: -30px; top: -30px; } .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4 { position: relative; display: block; width: 30px; height: 30px; cursor: pointer; -webkit-transition: -webkit-transform 0.18s; transition: -webkit-transform 0.18s; transition: transform 0.18s; transition: transform 0.18s, -webkit-transform 0.18s; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); } .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after, .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::before { -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); content: ""; position: absolute; top: 12px; left: 4px; width: 20px; height: 3px; -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); background-color: #fff; } .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after { -webkit-transform: rotate(-45deg); -ms-transform: rotate(-45deg); transform: rotate(-45deg); } .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4:hover { -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area { position: relative; -webkit-box-sizing: border-box; box-sizing: border-box; padding: 35px; height: 170px; background-color: rgba(0, 0, 0, 0.03); text-align: center; border: 1px dashed rgba(0, 0, 0, 0.08); overflow: hidden; } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 { display: block; margin: 0 auto 6px; width: 42px; height: 42px; overflow: hidden; } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-arrow { display: block; margin: 0 auto; width: 0; height: 0; border-bottom: 14.7px solid rgba(0, 0, 0, 0.3); border-left: 14.7px solid transparent; border-right: 14.7px solid transparent; } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-body { display: block; width: 12.6px; height: 14.7px; margin: 0 auto; background-color: rgba(0, 0, 0, 0.3); } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-bottom { -webkit-box-sizing: border-box; box-sizing: border-box; display: block; height: 12.6px; border: 6px solid rgba(0, 0, 0, 0.3); border-top: none; } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-hint { display: block; padding: 15px; font-size: 14px; color: #666; line-height: 30px; } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-no-supported-hint { display: block; position: absolute; top: 0; left: 0; padding: 30px; width: 100%; height: 60px; line-height: 30px; background-color: #eee; text-align: center; color: #666; font-size: 14px; } .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area:hover { cursor: pointer; border-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.05); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop { overflow: hidden; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left { float: left; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container { position: relative; display: block; width: 240px; height: 180px; background-color: #e5e5e0; overflow: hidden; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img { position: absolute; display: block; cursor: move; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade { -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18); box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18); position: absolute; background-color: rgba(241, 242, 243, 0.8); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-1 { top: 0; left: 0; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-2 { bottom: 0; right: 0; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate { position: relative; width: 240px; height: 18px; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i { display: block; width: 18px; height: 18px; border-radius: 100%; line-height: 18px; text-align: center; font-size: 12px; font-weight: bold; background-color: rgba(0, 0, 0, 0.08); color: #fff; overflow: hidden; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i:hover { -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); cursor: pointer; background-color: rgba(0, 0, 0, 0.14); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i:first-child { float: left; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i:last-child { float: right; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range { position: relative; margin: 30px 0 10px 0; width: 240px; height: 18px; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5, .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 { position: absolute; top: 0; width: 18px; height: 18px; border-radius: 100%; background-color: rgba(0, 0, 0, 0.08); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5:hover, .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6:hover { -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); cursor: pointer; background-color: rgba(0, 0, 0, 0.14); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5 { left: 0; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5::before { position: absolute; content: ""; display: block; left: 3px; top: 8px; width: 12px; height: 2px; background-color: #fff; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 { right: 0; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::before { position: absolute; content: ""; display: block; left: 3px; top: 8px; width: 12px; height: 2px; background-color: #fff; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::after { position: absolute; content: ""; display: block; top: 3px; left: 8px; width: 2px; height: 12px; background-color: #fff; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"] { display: block; padding-top: 5px; margin: 0 auto; width: 180px; height: 8px; vertical-align: top; background: transparent; -webkit-appearance: none; -moz-appearance: none; appearance: none; cursor: pointer; /* 滑块 ---------------------------------------------------------------*/ /* 轨道 ---------------------------------------------------------------*/ } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:focus { outline: none; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-webkit-slider-thumb { -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18); box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18); -webkit-appearance: none; appearance: none; margin-top: -3px; width: 12px; height: 12px; background-color: #61c091; border-radius: 100%; border: none; -webkit-transition: 0.2s; transition: 0.2s; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-moz-range-thumb { box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18); -moz-appearance: none; appearance: none; width: 12px; height: 12px; background-color: #61c091; border-radius: 100%; border: none; -webkit-transition: 0.2s; transition: 0.2s; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-ms-thumb { box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18); appearance: none; width: 12px; height: 12px; background-color: #61c091; border: none; border-radius: 100%; -webkit-transition: 0.2s; transition: 0.2s; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:active::-moz-range-thumb { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); width: 14px; height: 14px; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:active::-ms-thumb { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); width: 14px; height: 14px; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:active::-webkit-slider-thumb { -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23); margin-top: -4px; width: 14px; height: 14px; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-webkit-slider-runnable-track { -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); width: 100%; height: 6px; cursor: pointer; border-radius: 2px; border: none; background-color: rgba(68, 170, 119, 0.3); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-moz-range-track { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); width: 100%; height: 6px; cursor: pointer; border-radius: 2px; border: none; background-color: rgba(68, 170, 119, 0.3); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-ms-track { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12); width: 100%; cursor: pointer; background: transparent; border-color: transparent; color: transparent; height: 6px; border-radius: 2px; border: none; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-ms-fill-lower { background-color: rgba(68, 170, 119, 0.3); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]::-ms-fill-upper { background-color: rgba(68, 170, 119, 0.15); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:focus::-webkit-slider-runnable-track { background-color: rgba(68, 170, 119, 0.5); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:focus::-moz-range-track { background-color: rgba(68, 170, 119, 0.5); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:focus::-ms-fill-lower { background-color: rgba(68, 170, 119, 0.45); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type="range"]:focus::-ms-fill-upper { background-color: rgba(68, 170, 119, 0.25); } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right { float: right; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview { height: 150px; overflow: hidden; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item { position: relative; padding: 5px; width: 100px; height: 100px; float: left; margin-right: 16px; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item span { position: absolute; bottom: -30px; width: 100%; font-size: 14px; color: #bbb; display: block; text-align: center; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item img { position: absolute; display: block; top: 0; bottom: 0; left: 0; right: 0; margin: auto; padding: 3px; background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.15); overflow: hidden; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item.vicp-preview-item-circle { margin-right: 0; } .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item.vicp-preview-item-circle img { border-radius: 100%; } .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload { position: relative; -webkit-box-sizing: border-box; box-sizing: border-box; padding: 35px; height: 170px; background-color: rgba(0, 0, 0, 0.03); text-align: center; border: 1px dashed #ddd; } .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-loading { display: block; padding: 15px; font-size: 16px; color: #999; line-height: 30px; } .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap { margin-top: 12px; background-color: rgba(0, 0, 0, 0.08); border-radius: 3px; } .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress { position: relative; display: block; height: 5px; border-radius: 3px; background-color: #4a7; -webkit-box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3); box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3); -webkit-transition: width 0.15s linear; transition: width 0.15s linear; background-image: -webkit-linear-gradient( 135deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent ); background-image: linear-gradient( -45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent ); background-size: 40px 40px; -webkit-animation: vicp_progress 0.5s linear infinite; animation: vicp_progress 0.5s linear infinite; } .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress::after { content: ""; position: absolute; display: block; top: -3px; right: -3px; width: 9px; height: 9px; border: 1px solid rgba(245, 246, 247, 0.7); -webkit-box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7); box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7); border-radius: 100%; background-color: #4a7; } .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-error, .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-success { height: 100px; line-height: 100px; } .vue-image-crop-upload .vicp-wrap .vicp-operate { position: absolute; right: 20px; bottom: 20px; } .vue-image-crop-upload .vicp-wrap .vicp-operate a { position: relative; float: left; display: block; margin-left: 10px; width: 100px; height: 36px; line-height: 36px; text-align: center; cursor: pointer; font-size: 14px; color: #4a7; border-radius: 2px; overflow: hidden; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .vue-image-crop-upload .vicp-wrap .vicp-operate a:hover { background-color: rgba(0, 0, 0, 0.03); } .vue-image-crop-upload .vicp-wrap .vicp-error, .vue-image-crop-upload .vicp-wrap .vicp-success { display: block; font-size: 14px; line-height: 24px; height: 24px; color: #d10; text-align: center; vertical-align: top; } .vue-image-crop-upload .vicp-wrap .vicp-success { color: #4a7; } .vue-image-crop-upload .vicp-wrap .vicp-icon3 { position: relative; display: inline-block; width: 20px; height: 20px; top: 4px; } .vue-image-crop-upload .vicp-wrap .vicp-icon3::after { position: absolute; top: 3px; left: 6px; width: 6px; height: 10px; border-width: 0 2px 2px 0; border-color: #4a7; border-style: solid; -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); content: ""; } .vue-image-crop-upload .vicp-wrap .vicp-icon2 { position: relative; display: inline-block; width: 20px; height: 20px; top: 4px; } .vue-image-crop-upload .vicp-wrap .vicp-icon2::after, .vue-image-crop-upload .vicp-wrap .vicp-icon2::before { content: ""; position: absolute; top: 9px; left: 4px; width: 13px; height: 2px; background-color: #d10; -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); } .vue-image-crop-upload .vicp-wrap .vicp-icon2::after { -webkit-transform: rotate(-45deg); -ms-transform: rotate(-45deg); transform: rotate(-45deg); } .e-ripple { position: absolute; border-radius: 100%; background-color: rgba(0, 0, 0, 0.15); background-clip: padding-box; pointer-events: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-transform: scale(0); -ms-transform: scale(0); transform: scale(0); opacity: 1; } .e-ripple.z-active { opacity: 0; -webkit-transform: scale(2); -ms-transform: scale(2); transform: scale(2); -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; transition: opacity 1.2s ease-out, transform 0.6s ease-out; transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; } </style>