手机端上传图片前进行裁剪

2020-04-23  本文已影响0人  WangYatao

1.创建组件ImageCropper

需要安装lrz 这里使用npm安装

npm i lrz

<style scoped>
    .box {
        display: flex;
        position: relative;
    }

    .box-f1 {
        flex: 1;
    }

    .box-ac {
        align-items: center
    }

    .box-jc {
        justify-content: center
    }

    .box-ver {
        flex-direction: column
    }

    .cropper-page {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 10;
        background-color: #fff;
        overflow: hidden;
    }

    .cover {
        color: #FFF;
        font-size: .4rem;
        background-color: rgba(0, 0, 0, 0.2);
    }

    .cropper-box {
        border: 1px dashed #FFF;
    }

    .cropper-img {
        position: absolute;
        z-index: -1;
    }

    input[type="file"] {
        opacity: 0;
        position: fixed;
        top: -1000px;
        left: -1000px;
    }

    .btn {
        font-size: .3rem;
        padding: .1rem .3rem;
        border: .02rem solid;
        color: #FFF;
        border-radius: .1rem;
    }

    .btn .sure {
        color: #45b795;
    }
</style>

<template>
    <div ref="cropperPage" class="cropper-page" v-show="isShow">
        <div @click="cancel()" class="icon icon-back"></div>
        <input ref="file" type="file" accept="image/*" @change="readImage">
        <img alt="" class="cropper-img" :style="imageStyle" ref="img">
        <div class="cover box box-ac box-jc" :style="{height: coverHeight + 'px'}">
            请调整图片
        </div>
        <div ref="cropBox" class="cropper-box" @touchstart.prevent="touchStart" @touchmove.prevent="touchMove"></div>
        <div class="cover cover box box-ac box-jc" :style="{height: coverHeight + 'px'}">
            <div class="box box-f1 box-jc box-fh">
                <div class="btn" @click="checkPhoto">重选</div>
            </div>
            <div class="box box-f1 box-jc box-fh">
                <div class="btn sure" @click="confirm">确定</div>
            </div>
        </div>
    </div>
</template>

<script>
    // import EXIF from '../assets/js/exif-small'
    import lrz from 'lrz'

    const getDinstance = function (point0, point1) {
        return Math.sqrt(Math.pow(point0.pageY - point1.pageY, 2) + Math.pow(point0.pageX - point1.pageX, 2))
    }
    export default {
        name: 'imageCropper',
        props: {
            callback: {
                type: Function,
                default() {
                }
            },
            cropperConfig: {
                type: Object,
                default() {
                    return {
                        width: 1,
                        height: 1,
                        quality: 0.7,
                        maxWidth: 640
                    }
                }
            }
        },
        data() {
            return {
                coverHeight: 0,
                cropperHeight: 0,
                imgInitTop: 0,
                amplitude: 0,
                imageState: {
                    left: 0,
                    top: 0,
                    scale: 1,
                    width: 0,
                    height: 0,
                    originX: 0,
                    originY: 0
                },
                distance: 0,
                imageStyle: {
                    top: '0',
                    transform: 'translate3d(0px, 0px, 0px) scale(1)',
                    transformOrigin: 'left top'
                },
                cropBoxRect: {},
                touchPos: {
                    x: 0,
                    y: 0
                },
                isShow: false,
                minScale: 0,
                info: '',
                orientation: ''
            }
        },
        watch: {
            'imageState': {
                handler(val) {
                    // console.log(val)
                    this.imageStyle.transform = 'translate3d(-' + val.left + 'px, -' + val.top + 'px, 0px) scale(' + val.scale + ')'
                },
                deep: true
            }
        },
        methods: {
            cancel() {
                this.file = null
                this.isShow = false
            },
            checkPhoto() {
                this.$refs.file.click()
            },
            readImage($event) {
                var self = this
                var file = $event.target.files[0]
                lrz(file)
                    .then(rst => {
                        self.orientation = 1
                        self.$refs.img.onload = () => {
                            self.initCropper()
                        }
                        self.$refs.img.src = rst.base64
                        $event.target.value = null
                    })
                // .always(() => {
                //   this.$store.commit('isLoading', false)
                // })
            },
            // readImage ($event) {
            //   var self = this
            //   var file = $event.target.files[0]
            //   var reader = new window.FileReader()
            //   reader.onload = () => {
            //     EXIF.getData(file, function () {
            //       let orientation = EXIF.getTag(this, 'Orientation')
            //       if (!orientation) orientation = 1
            //       self.orientation = orientation
            //       self.$refs.img.onload = () => {
            //         self.initCropper()
            //       }
            //       self.$refs.img.src = reader.result
            //       $event.target.value = null
            //     })
            //   }
            //   reader.readAsDataURL(file)
            // },
            initCropper() {
                this.isShow = true // 显示裁剪界面
                this.$nextTick(() => {
                    let cropperPage = this.$refs.cropperPage
                    let pageWidth = cropperPage.clientWidth
                    let pageHeight = cropperPage.clientHeight
                    let cropBox = this.$refs.cropBox
                    let cropBoxWidth = cropBox.clientWidth
                    let cropBoxHeight = Math.floor(cropBoxWidth * (+this.cropperConfig.height) / (+this.cropperConfig.width))
                    this.$refs.cropBox.style.height = cropBoxHeight + 'px'
                    this.coverHeight = (pageHeight - cropBoxHeight) / 2
                    let cropBoxTop = this.coverHeight
                    this.imageState.left = 0
                    this.imageState.top = 0
                    this.imageStyle.top = cropBoxTop + 'px'
                    this.cropBoxRect = {
                        left: 0,
                        top: cropBoxTop,
                        width: pageWidth,
                        height: cropBoxHeight
                    }
                    let img = this.$refs.img
                    var width = this.imageState.width = img.naturalWidth
                    var height = this.imageState.height = img.naturalHeight
                    // 计算imageState
                    if (width > height) {
                        this.minScale = this.imageState.scale = this.cropBoxRect.height / height
                        this.imageState.left = (width * this.imageState.scale - this.cropBoxRect.width) / 2
                    } else {
                        this.minScale = this.imageState.scale = this.cropBoxRect.width / width
                        this.imageState.top = (height * this.imageState.scale - this.cropBoxRect.height) / 2
                    }
                })
            },
            confirm() {
                let self = this
                let imageState = this.imageState
                let cropBoxRect = this.cropBoxRect
                // 导出图片的最大宽度
                let maxWidth = this.cropperConfig.maxWidth
                let scale2 = maxWidth / cropBoxRect.width
                let scale = imageState.scale * scale2
                let width = cropBoxRect.width * scale2
                let height = cropBoxRect.height * scale2
                let left = imageState.left * scale2
                let top = imageState.top * scale2
                let image = this.$refs.img
                let canvas = document.createElement('canvas')
                let ctx = canvas.getContext('2d')
                // ios 的照片有拍摄的角度信息 参考 http://www.bcty365.com/content-142-3055-1.html
                let orientation = this.orientation
                switch (orientation) {
                    case 1:
                        canvas.width = width
                        canvas.height = height
                        ctx.drawImage(image, left / scale, top / scale, width / scale, height / scale, 0, 0, width, height)
                        break
                    case 6:
                        canvas.width = height
                        canvas.height = width
                        ctx.rotate(90 * Math.PI / 180)
                        ctx.drawImage(image, left / scale, top / scale, width / scale, height / scale, 0, -height, width, height)
                        break
                    case 8:
                        canvas.width = height
                        canvas.height = width
                        ctx.rotate(-90 * Math.PI / 180)
                        ctx.drawImage(image, left / scale, top / scale, width / scale, height / scale, -width, 0, width, height)
                        break
                    case 3:
                        canvas.width = width
                        canvas.height = height
                        ctx.rotate(180 * Math.PI / 180)
                        ctx.drawImage(image, left / scale, top / scale, width / scale, height / scale, -width, -height, width, height)
                        break
                }
                let dataUrl = canvas.toDataURL('image/jpeg', this.cropperConfig.quality)
                self.callback(dataUrl)
                self.isShow = false
            },
            getFocalPoint(point0, point1) {
                return {
                    x: (point0.pageX + point1.pageX) / 2,
                    y: (point0.pageY + point1.pageY) / 2
                }
            },
            touchStart(event) {
                var fingerCount = event.touches.length
                if (fingerCount) {
                    // 记录触摸初始位置
                    let touchEvent = event.touches[0]
                    this.touchPos = {
                        x: touchEvent.clientX,
                        y: touchEvent.clientY
                    }
                }
                if (fingerCount >= 2) {
                    // 获取两点距离、中点位置;两点距离old/new=放大倍数;中点位置,缩放中心;
                    let point0 = event.touches[0]
                    let point1 = event.touches[1]
                    this.distance = getDinstance(point0, point1)
                    this.touchPos = this.getFocalPoint(point0, point1)
                    // 设置缩放倍数,
                }
            },
            touchMove(event) {
                // 根据触摸点位移,移动图片,重置触摸点位置
                var fingerCount = event.touches.length
                var touchEvent = event.touches[0]
                if (fingerCount === 1) {
                    let distX = touchEvent.pageX - this.touchPos.x
                    let distY = touchEvent.pageY - this.touchPos.y
                    let newX = this.imageState.left - distX
                    let newY = this.imageState.top - distY
                    let scale = this.imageState.scale
                    // alert(scale)
                    let maxX = this.imageState.width * scale - this.cropBoxRect.width
                    let maxY = this.imageState.height * scale - this.cropBoxRect.height
                    this.imageState.left = newX < 0 ? 0 : (newX > maxX ? maxX : newX)
                    this.imageState.top = newY < 0 ? 0 : (newY > maxY ? maxY : newY)
                    this.touchPos.x = touchEvent.pageX
                    this.touchPos.y = touchEvent.pageY
                } else if (fingerCount > 1) {
                    let point0 = event.touches[0]
                    let point1 = event.touches[1]
                    let distance = getDinstance(point0, point1)
                    let zoom = distance / this.distance
                    let scale = zoom * this.imageState.scale
                    let maxX = this.imageState.width * scale - this.cropBoxRect.width
                    let maxY = this.imageState.height * scale - this.cropBoxRect.height
                    let touchPos = this.getFocalPoint(point0, point1)
                    let newX = zoom * (this.imageState.left + touchPos.x) - touchPos.x
                    let newY = zoom * ((this.imageState.top - this.imgInitTop) + touchPos.y) - touchPos.y + this.imgInitTop
                    // 限制缩放
                    // 图片新位置:由中点位置确认;(新位置到中点)/(旧位置到中点)=(new scale)/(old scale)
                    // newLeft - touchPos.x = (distance / this.distance) * (oldLetf - touchPos.x)
                    // oldLeft = 0 - this.imageState.left
                    // oldTop = imgInitTop - this.imageState.top
                    this.distance = distance
                    if (scale < this.minScale) {
                        this.imageState.scale = this.minScale
                    } else {
                        this.imageState.scale = scale
                        this.imageState.left = newX < 0 ? 0 : (newX > maxX ? maxX : newX)
                        this.imageState.top = newY < 0 ? 0 : (newY > maxY ? maxY : newY)
                    }
                    this.touchPos = touchPos
                }
            }
        }
    }
</script>

使用方法

参数说明:
cropperConfig: {
  width: 1, // 裁剪宽度(比例)
  height: 1, // 裁剪高度(比例)
  quality: 0.7, // 图片质量(0~1之间)
  maxWidth: 750 // 导出的图片的最大宽度
}
<template>
    <div class="container">
        <img class="box" v-for="(value, key) in images" :value="value" :key="key" :src="value" />
        <div class="setting">
            <div class="box box-ver">
                <div class="box box-fh line box-ac">
                    裁剪宽度:
                    <input class="box box-f1" placeholder="width" type="tel" v-model="cropperConfig.width" />
                </div>
                <div class="box  box-fh line box-ac">
                    裁剪高度:
                    <input class="box box-f1" placeholder="height" type="tel" v-model="cropperConfig.height" />
                </div>
                <div class="box  box-fh line box-ac">
                    图片质量:
                    <input class="box box-f1" placeholder="height" type="tel" v-model="cropperConfig.quality" />
                </div>
                <div class="box  box-fh line box-ac">
                    最大宽度:
                    <input class="box box-f1" placeholder="height" type="tel" v-model="cropperConfig.maxWidth" />
                </div>
            </div>
            <div class="box box-ac box-jc">
                <div @click="selectFile()" class="btn">选择图片</div>
                <div @click="uploadImg()" class="btn">上传</div>
            </div>
        </div>
        <image-cropper ref="imageCropper" :cropperConfig="cropperConfig" :callback="loadImage"></image-cropper>
    </div>
</template>
<script>
    import imageCropper from '@/components/imageCropper'
    export default {
        name: 'index',
        components: {
            imageCropper
        },
        data () {
            return {
                images: [],
                cropperConfig: {
                    width: 1,
                    height: 1,
                    quality: 0.7,
                    maxWidth: 750
                }
            }
        },
        mounted () {
        },
        methods: {
            selectFile () {
                this.$refs.imageCropper.checkPhoto()
            },
            // 图片裁剪之后的回调
            loadImage (data) {
                this.images.push(data)
            },
            uploadImg () {
                alert('上传')
            }
        }
    }
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    .container{
        height:100%;
        overflow-y: scroll;
        -webkit-overflow-scrolling: touch;
        padding-top:4rem;
    }
    .container img{
        display:block;
        width:50%;
        float:left;
    }
    .setting{
        position:fixed;
        width:100%;
        top:0;
        left:0;
        border-bottom:1px dashed #45b795;
        padding-bottom:.1rem;
    }
    .setting .line{
        margin:0 .2rem;
        height:.75rem;
        color:#45b795;
    }
    .setting input{
        display: block;
        height:.5rem;
        padding:0 .15rem;
        font-size:.32rem;
        border:1px solid #45b795;
        outline: none;
    }
    .setting .btn{
        color:#FFF;
        margin:.1rem .5rem;
        width:2rem;
        height:.6rem;
        line-height:.6rem;
        font-size:.32rem;
        text-align: center;
        background-color: #45b795;
        border-radius:.1rem;
    }
</style>

原文:https://segmentfault.com/a/1190000009997448

上一篇下一篇

猜你喜欢

热点阅读