组件的调用
<image-cropper
v-show="imagecropperShow"
:key="imagecropperKey"
:width="100"
:height="100"
url="http://127.0.0.1:17240/upload/uploadImage"
lang-type="en"
@close="close"
@crop-upload-success="cropSuccess"
/>
后端接口
@PostMapping("/uploadImage")
public MsgResult load(@RequestBody(required = false) MultipartFile multipartFile) {
if (multipartFile==null||multipartFile.getSize()==0)
return new MsgResult(true,CodeEnum.ERROR.code(),MsgEnum.BAD_PARAM.msg());
try {
InputStream is = multipartFile.getInputStream();
FileWriter fw = new FileWriter(imageFilePath + multipartFile.getOriginalFilename());
boolean b = MyIoUtils.writtenToLocal(is, fw);
if (b)
return new MsgResult(true, CodeEnum.SUCCESS.code(), MsgEnum.SUCCESS.msg());
} catch (IOException e) {
e.printStackTrace();
}
return new MsgResult(false, CodeEnum.ERROR.code(), MsgEnum.ERROR.msg());
}
组件的prop参数中的field必须和后端接收数据的变量名一致,否则会导致接收不到参数;
可以在组件调用的时候赋值
组件本体
<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: 'multipartFile'
},
// 原名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,
// 浏览器是否支持触屏事件
// eslint-disable-next-line no-prototype-builtins
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>