join_admin/src/components/ImageCropper/index.vue

1779 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>