cmc-web/packages/common/components/image-cropper/index.vue

1451 lines
41 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="vue-image-crop-upload" v-show="value">
<div class="vicp-wrap">
<div class="vicp-close" @click="off">
<i class="vicp-icon4"></i>
</div>
<div class="vicp-step1" v-show="step == 1">
<div class="vicp-drop-area" @dragleave="preventDefault" @dragover="preventDefault" @dragenter="preventDefault" @click="handleClick" @drop="handleChange">
<i class="vicp-icon1" v-show="loading != 1">
<i class="vicp-icon1-arrow"></i>
<i class="vicp-icon1-body"></i>
<i class="vicp-icon1-bottom"></i>
</i>
<span class="vicp-hint" v-show="loading !== 1">{{ lang.hint }}</span>
<span class="vicp-no-supported-hint" v-show="!isSupported">{{ lang.noSupported }}</span>
<input type="file" v-show="false" v-if="step == 1" @change="handleChange" ref="fileinput" />
</div>
<div class="vicp-error" v-show="hasError"><i class="vicp-icon2"></i> {{ errorMsg }}</div>
<div class="vicp-operate">
<a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a>
</div>
</div>
<div class="vicp-step2" v-if="step == 2">
<div class="vicp-crop">
<div class="vicp-crop-left" v-show="true">
<div class="vicp-img-container">
<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"
ref="img"
/>
<div class="vicp-img-shade vicp-img-shade-1" :style="sourceImgShadeStyle"></div>
<div class="vicp-img-shade vicp-img-shade-2" :style="sourceImgShadeStyle"></div>
</div>
<div class="vicp-range">
<input type="range" :value="scale.range" step="1" min="0" max="100" @change="zoomChange" />
<i @mousedown="startZoomSub" @mouseout="endZoomSub" @mouseup="endZoomSub" class="vicp-icon5"></i>
<i @mousedown="startZoomAdd" @mouseout="endZoomAdd" @mouseup="endZoomAdd" class="vicp-icon6"></i>
</div>
<div class="vicp-rotate" v-if="!noRotate">
<i @click="rotateImg">↻</i>
</div>
</div>
<div class="vicp-crop-right" v-show="true">
<div class="vicp-preview">
<div class="vicp-preview-item" v-if="!noSquare">
<img :src="createImgUrl" :style="previewStyle" />
<span>{{ lang.preview }}</span>
</div>
<div class="vicp-preview-item vicp-preview-item-circle" v-if="!noCircle">
<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 class="vicp-step3" v-if="step == 3">
<div class="vicp-upload">
<span class="vicp-loading" v-show="loading === 1">{{ lang.loading }}</span>
<div class="vicp-progress-wrap">
<span class="vicp-progress" v-show="loading === 1" :style="progressStyle"></span>
</div>
<div class="vicp-error" v-show="hasError"><i class="vicp-icon2"></i> {{ errorMsg }}</div>
<div class="vicp-success" v-show="loading === 2"><i class="vicp-icon3"></i> {{ 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" :width="width" :height="height" ref="canvas"></canvas>
</div>
</div>
</template>
<script>
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: {
default: 0
},
// 显示该控件与否
value: {
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'
},
// 图片背景 jpg情况下生效
imgBgc: {
type: String,
default: '#fff'
},
// 是否支持跨域
withCredentials: {
type: Boolean,
default: false
}
},
data() {
const that = this,
{ imgFormat, langType, langExt, width, height } = that,
allowImgFormat = ['jpg', 'png'],
tempImgFormat = allowImgFormat.indexOf(imgFormat) === -1 ? 'jpg' : imgFormat,
lang = language[langType] ? language[langType] : language.en,
mime = mimes[tempImgFormat]
let isSupported = true
// 规范图片格式
that.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
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,
top = scale.y + sourceImgMasking.y + 'px',
left = scale.x + sourceImgMasking.x + 'px'
return {
top,
left,
width: scale.width + 'px',
height: scale.height + 'px' // 兼容 Opera
}
},
// 原图蒙版属性
sourceImgMasking() {
const { width, height, ratio, sourceImgContainer } = this,
sic = sourceImgContainer,
sicRatio = sic.width / sic.height // 原图容器宽高比
let x = 0,
y = 0,
w = sic.width,
h = sic.height,
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,
sic = sourceImgContainer,
sim = sourceImgMasking,
w = sim.width == sic.width ? sim.width : (sic.width - sim.width) / 2,
h = sim.height == sic.height ? sim.height : (sic.height - sim.height) / 2
return {
width: w + 'px',
height: h + 'px'
}
},
previewStyle() {
const { width, height, ratio, previewContainer } = this,
pc = previewContainer
let w = pc.width,
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()
}
}
},
methods: {
// 点击波纹效果
ripple(e) {
effectRipple(e)
},
// 关闭控件
off() {
setTimeout(() => {
this.$emit('input', false)
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 that = this,
{ lang, maxSize } = that
// 仅限图片
if (file.type.indexOf('image') === -1) {
that.hasError = true
that.errorMsg = lang.error.onlyImg
return false
}
// 超出大小
if (file.size / 1024 > maxSize) {
that.hasError = true
that.errorMsg = lang.error.outOfSize + maxSize + 'kb'
return false
}
return true
},
// 重置控件
reset() {
const that = this
that.loading = 0
that.hasError = false
that.errorMsg = ''
that.progress = 0
},
// 设置图片源
setSourceImg(file) {
const that = this,
fr = new FileReader()
fr.onload = function (e) {
that.sourceImgUrl = fr.result
that.startCrop()
}
fr.readAsDataURL(file)
},
// 剪裁前准备工作
startCrop() {
const that = this,
{ width, height, ratio, scale, sourceImgUrl, sourceImgMasking, lang } = that,
sim = sourceImgMasking,
img = new Image()
img.src = sourceImgUrl
img.onload = function () {
const nWidth = img.naturalWidth,
nHeight = img.naturalHeight,
nRatio = nWidth / nHeight
let w = sim.width,
h = sim.height,
x = 0,
y = 0
// 图片像素不达标
if (nWidth < width || nHeight < height) {
that.hasError = true
that.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.minWidth = w
scale.minHeight = h
scale.maxWidth = nWidth * sim.scale
scale.maxHeight = nHeight * sim.scale
scale.naturalWidth = nWidth
scale.naturalHeight = nHeight
that.sourceImg = img
that.createImg()
that.setStep(2)
}
},
// 鼠标按下图片准备移动
imgStartMove(e) {
e.preventDefault()
// 支持触摸事件,则鼠标事件无效
if (this.isSupportTouch && !e.targetTouches) {
return false
}
const et = e.targetTouches ? e.targetTouches[0] : e,
{ sourceImgMouseDown, scale } = this,
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,
{
sourceImgMouseDown: { on, mX, mY, x, y },
scale,
sourceImgMasking
} = this,
sim = sourceImgMasking,
nX = et.screenX,
nY = et.screenY,
dX = nX - mX,
dY = nY - mY
let rX = x + dX,
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
},
// 顺时针旋转图片
rotateImg(e) {
const {
sourceImg,
scale: { naturalWidth, naturalHeight }
} = this,
width = naturalHeight,
height = naturalWidth,
canvas = this.$refs.canvas,
ctx = canvas.getContext('2d')
canvas.width = width
canvas.height = height
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = 'rgba(0,0,0,0)'
ctx.fillRect(0, 0, width, height)
ctx.translate(width, 0)
ctx.rotate((Math.PI * 90) / 180)
ctx.drawImage(sourceImg, 0, 0, naturalWidth, naturalHeight)
const imgUrl = canvas.toDataURL(mimes.png)
this.sourceImgUrl = imgUrl
this.startCrop()
},
// 按钮按下开始放大
startZoomAdd(e) {
const that = this,
{ scale } = that
scale.zoomAddOn = true
function zoom() {
if (scale.zoomAddOn) {
const range = scale.range >= 100 ? 100 : ++scale.range
that.zoomImg(range)
setTimeout(function () {
zoom()
}, 60)
}
}
zoom()
},
// 按钮松开或移开取消放大
endZoomAdd(e) {
this.scale.zoomAddOn = false
},
// 按钮按下开始缩小
startZoomSub(e) {
const that = this,
{ scale } = that
scale.zoomSubOn = true
function zoom() {
if (scale.zoomSubOn) {
const range = scale.range <= 0 ? 0 : --scale.range
that.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 that = this,
{ sourceImgMasking, sourceImgMouseDown, scale } = this,
{ maxWidth, maxHeight, minWidth, minHeight, width, height, x, y, range } = scale,
sim = sourceImgMasking,
// 蒙版宽高
sWidth = sim.width,
sHeight = sim.height,
// 新宽高
nWidth = minWidth + ((maxWidth - minWidth) * newRange) / 100,
nHeight = minHeight + ((maxHeight - minHeight) * newRange) / 100
// 新坐标(根据蒙版中心点缩放)
let nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x),
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(function () {
if (scale.range == newRange) {
that.createImg()
}
}, 300)
},
// 生成需求图片
createImg(e) {
const that = this,
{
imgFormat,
imgBgc,
mime,
sourceImg,
scale: { x, y, width, height },
sourceImgMasking: { scale }
} = that,
canvas = that.$refs.canvas,
ctx = canvas.getContext('2d')
if (e) {
// 取消鼠标按下移动状态
that.sourceImgMouseDown.on = false
}
canvas.width = that.width
canvas.height = that.height
ctx.clearRect(0, 0, that.width, that.height)
if (imgFormat == 'png') {
ctx.fillStyle = 'rgba(0,0,0,0)'
} else {
// 如果jpg 为透明区域设置背景,默认白色
ctx.fillStyle = imgBgc
}
ctx.fillRect(0, 0, that.width, that.height)
ctx.drawImage(sourceImg, x / scale, y / scale, width / scale, height / scale)
that.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 that = this,
{ lang, imgFormat, mime, url, params, headers, field, ki, createImgUrl, withCredentials } = this,
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 = function (event) {
if (event.lengthComputable) {
that.progress = (100 * Math.round(event.loaded)) / event.total
}
}
// 上传文件
that.reset()
that.loading = 1
that.setStep(3)
new Promise(function (resolve, reject) {
const client = new XMLHttpRequest()
client.open('POST', url, true)
client.withCredentials = withCredentials
client.onreadystatechange = function () {
if (this.readyState !== 4) {
return
}
if (this.status === 200 || this.status === 201) {
resolve(JSON.parse(this.responseText))
} else {
reject(this.status)
}
}
client.upload.addEventListener('progress', uploadProgress, false) // 监听进度
// 设置header
if (typeof headers == 'object' && headers) {
Object.keys(headers).forEach(k => {
client.setRequestHeader(k, headers[k])
})
}
client.send(fmData)
}).then(
// 上传成功
function (resData) {
if (that.value) {
that.loading = 2
that.$emit('crop-upload-success', resData, field, ki)
}
},
// 上传失败
function (sts) {
if (that.value) {
that.loading = 3
that.hasError = true
that.errorMsg = lang.fail
that.$emit('crop-upload-fail', sts, field, ki)
}
}
)
}
},
created() {
// 绑定按键esc隐藏此插件事件
document.addEventListener('keyup', e => {
if (this.value && (e.key == 'Escape' || e.keyCode == 27)) {
this.off()
}
})
}
}
</script>
<style>
@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>