最近某个项目,有个内容发布模块,支持上传封面图,封面图需要按照比例裁剪,自然就用上了vue-cropper这个裁剪插件了。
因为是弹窗裁剪,在实现的时候,要考虑到设备低分辨率屏幕(比如1366*768这种笔记本还是有在使用的),当图片尺寸过大的时候(如高清图1920*1080),如何更好的在弹窗显示。效果如图。

接下来看代码,使用了vue+element ui。
<template>
<div>
<el-dialog
title="图片裁剪"
ref="dialog"
v-bind="$attrs"
v-on="$listeners"
:visible.sync="dialogVisible"
:top="dialogTop"
:width="dialogWidth+'px'"
class="cropper-dialog"
:close-on-press-escape="false"
:before-close="closeDialog"
:close-on-click-modal="false"
>
<main :style="cropperOutBoxStyle">
<div :style="cropperWrapperStyle">
<vue-cropper
ref="cropper"
v-bind="$attrs"
v-on="$listeners"
:img="cropperOption.img"
:full="cropperOption.full"
:auto-crop="cropperOption.autoCrop"
:output-type="cropperOption.outputType"
:fixed-box="cropperOption.fixedBox"
:fixed="true"
:fixed-number="photoRatio"
:center-box="true"
:can-move="true"
:can-move-box="true"
/>
</div>
</main>
<div
class="cropper-operate"
:style="{width:cropperWrapperStyle.width}"
>
<div class="flexItem">
<i
class="el-icon-refresh-right"
@click="handleRotateRight"
/>
<i
class="el-icon-zoom-in"
@click="changeScale(1)"
/>
<i
class="el-icon-zoom-out"
@click="changeScale(-1)"
/>
</div>
<!-- <a @click="reUpload" href="javascript:;" class="cropper-operate-btn">重新上传</a> -->
</div>
<footer slot="footer">
<el-button
size="small"
@click="doCancel"
>
取消
</el-button>
<el-button
size="small"
type="primary"
@click="doCrop"
:disabled="isUploading"
>
确认
</el-button>
</footer>
</el-dialog>
</div>
</template>
<script>
/**
* 图片裁剪组件
*/
import { VueCropper } from 'vue-cropper'
import dialogMixin from './js/dialogMixin'
const defaults = {
img: '',
autoCropWidth: 900, // 默认生成截图框宽度
autoCropHeight: 500, // 默认生成截图框高度
full: false, // 是否输出原图比例的截图
autoCrop: true, // 是否默认生成截图框
fixedBox: false, // 固定截图框大小 不允许改变
outputType: 'jpg' // 裁剪生成图片的格式 默认jpg 可选jpeg || png || webp
}
export default {
components: {
VueCropper
},
mixins: [dialogMixin],
props: {
// 图片裁剪属性配置,可参考http://docs.fed.qiweioa.cn/quickwork/qwb/index.html#/zh-CN/cropper
cropperConfig: {
type: Object,
default: () => {
return defaults
}
}
},
data() {
return {
isUploading: false,
dialogWidth: 960,
dialogHeight: 500,
dialogTop: '10vh'
}
},
computed: {
// 截图框的宽高比例
photoRatio() {
const { autoCropWidth, autoCropHeight } = this.cropperOption
return [autoCropWidth, autoCropHeight]
},
cropperOption() {
return Object.assign(defaults, this.cropperConfig)
},
/**
* 裁剪框外部容器样式,影响到裁剪框
*/
cropperWrapperStyle() {
const { width, height, zoom } = this.getSettingStyle()
return {
// width: (this.dialogWidth - 60) + 'px',
// height: (this.dialogHeight) + 'px',
width: width + 'px',
height: height + 'px',
transform: `scale(${zoom})`,
'transform-origin': '0 0',
'margin-left': 'auto',
'margin-right': ' auto'
}
},
/**
* 设置裁剪框容器外层盒子高度是因为scale缩放不会影响原来占据的空间
*/
cropperOutBoxStyle() {
const { height, zoom } = this.getSettingStyle()
return {
height: height * zoom + 'px'
}
}
},
watch: {
visible() {
this.isUploading = false
this.$nextTick(() => {
const dialog = this.$refs.dialog.$el.querySelector('.el-dialog')
this.dialogTop = dialog.clientHeight >= window.innerHeight ? '0' : '10vh'
})
}
},
methods: {
/**
* 根据配置重新计算获取裁剪框宽高缩放
* 0.2,因为默认生成截图框宽度是外部容器的80%,所以外部容器要增加20%
* gutter, 左右距离弹窗框边距
* zoom,主要是为了保障大图裁剪的时候尺寸过大撑开弹窗而设置缩放
*/
getSettingStyle() {
const gutter = 60
const { autoCropWidth, autoCropHeight, img } = this.cropperOption
// 当然,还可以再优化一下,如果图片比弹窗小,不缩放。
// const imgInstance = new Image()
// imgInstance.src = img
// imgInstance.onload = () => {
// console.log('选择的图片宽:' + imgInstance.width, '\n选择的图片高:' + imgInstance.height)
// }
const zoom = (this.dialogWidth - gutter) / (autoCropWidth + autoCropWidth * 0.2) < 1 ? (this.dialogWidth - gutter) / (autoCropWidth + autoCropWidth * 0.2) : 1
const width = autoCropWidth + autoCropWidth * 0.2
const height = autoCropHeight + autoCropHeight * 0.2
return {
zoom,
width,
height
}
},
// 图片缩放
changeScale(n) {
this.$refs.cropper.changeScale(n)
},
// 图片旋转
handleRotateRight() {
this.$refs.cropper.rotateRight()
},
/**
* blob转file对象,如果后端上传接口需要file对象,用这个方法转换
*/
blobToFile(imgData) {
const file = new File([imgData.blob], imgData.fileName, {
type: imgData.fileType
})
// console.log('blob转file对象', file)
return file
},
// 获取裁剪的图片
async doCrop() {
this.isUploading = true
let base64 = null
let blob = null
await new Promise((resolve) => {
this.$refs.cropper.getCropData((data) => { // 获取截取图片的base64数据
base64 = data
this.$emit('getCropData', data)
return resolve(data)
})
})
await new Promise((resolve) => {
this.$refs.cropper.getCropBlob((data) => { // 获取截取图片的blob数据
blob = data
this.$emit('getCropBlob', data)
return resolve(data)
})
})
const { fileName, fileType, fileSize } = this.cropperOption
const imgData = {
blob,
fileName,
fileType
}
// console.log('base64:', base64, 'blob:', blob, 'file:', this.blobToFile(imgData))
this.$emit('confirm', {
base64,
blob,
file: this.blobToFile(imgData),
fileName,
fileSize,
fileType
})
},
/**
* 重新上传
*/
reUpload() {
this.$emit('reUpload')
},
doCancel() {
this.closeDialog()
this.$emit('cancel')
},
closeDialog() {
this.dialogVisible = false
}
}
}
</script>
dialogMixin.js
/**
* 弹窗mixin
*/
export default {
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
dialogVisible: false
}
},
watch: {
visible: {
handler(val) {
// console.log('visible', val)
this.dialogVisible = val
},
immediate: true
},
dialogVisible(val) {
if (val !== this.visible) {
this.$emit('update:visible', val)
}
}
}
}
<style lang="less" scoped>
.cropper-operate {
display: flex;
margin-left: auto;
margin-right: auto;
padding-top: 16px;
&-btn {
color: #3858e6;
}
i {
margin-right: 5px;
font-size: 20px;
color: #999;
cursor: pointer;
}
}
</style>
开发过程遇到的问题
刚开始的时候,使用的是zoom缩放,不过,想象很美好,现实有缺陷,zoom并不是w3c标准,caniuse.com网站显示,Firefox全系列都不支持,而我们考虑用户可能使用Firefox就只能另寻他法了


幸好,css3增加了强大的transform属性,transform的scale则是用来实现缩放的。scale的一些常见使用是图片hover缩放,带来的交互体验不错,也用于chrome不支持10px及以下像素字体大小,通过缩放实现。
zoom和scale除了兼容性的差异,zoom是整体空间缩放,而scale缩放是不影响原来的空间的,即使缩小,空间亦不会缩小。效果如下图。

要想实现zoom的效果,那么可以设置缩放区域父元素的宽高即可。就是上面代码中的这一段。
/**
* 设置裁剪框容器外层盒子高度是因为scale缩放不会影响原来占据的空间
*/
cropperOutBoxStyle() {
const { height, zoom } = this.getSettingStyle()
return {
height: height * zoom + 'px'
}
}
zoom和scale性能
因为zoom是实际改变物体大小的,而且这种改变会影响到其他元素,所以使用zoom的时候会引起浏览器重新渲染,性能较低。而scale是单独只改变目标元素,并不会影响到其他的元素,所以性能会好一些。
也就是说zoom会一起重排重绘,scale只会引起重绘,zoom的渲染性能会比较差。
结语
cropper.js应该是支持按照图片原尺寸比例大小输出裁剪的(不受宽高设置影响),但是试了好几个属性配置,还是不行,因此就想到了使用缩放来解决比例问题。
发表评论