vue中实现使用相框点击拍照,canvas进行前端图片合并下载

拍照和相框合成,下载图片dome

一、canvas介绍

Canvas是一个HTML5元素,它提供了一个用于在网页上绘制图形、图像和动画的2D渲染上下文。Canvas可以用于创建各种图形,如线条、矩形、圆形、文本等,并且可以通过JavaScript进行编程操作。

Canvas元素本身是一个矩形框,可以通过CSS样式进行样式设置。在Canvas上绘制图形时,需要先获取Canvas的2D渲染上下文,然后通过上下文的方法来进行绘制。

二、navigator.mediaDevices.getUserMedia介绍

navigator.mediaDevices.getUserMedia是一个Web API,它允许网页访问用户的媒体设备,如摄像头和麦克风。这个API返回一个Promise对象,成功后会resolve回调一个MediaStream对象。

使用navigator.mediaDevices.getUserMedia调用系统原生摄像头功能时,需要调用其getUserMedia方法并传入一个包含媒体类型约束的约束对象。这个约束对象可以包含音频、视频或两者都包含。 

navigator.mediaDevices.getUserMedia({ audio: true, video: true })  
  .then(function(stream) {  
    // 在这里使用媒体流  
  })  
  .catch(function(err) {  
    // 处理错误  
  });

如果用户同意,getUserMedia方法会返回一个包含音频和视频轨道的MediaStream对象。我们可以在then回调函数中使用这个媒体流。如果用户拒绝访问权限,或者需要的媒体源不可用,promise会reject回调一个PermissionDeniedError或者NotFoundError。

三、拍照下载图片功能

1:拍照画布
<!-- 拍照canvas -->
<canvas  style="display: none;"  ref="canvasCamera" class="canvas"></canvas>
2:显示调用摄像头效果
 <video ref="photoVideo" autoplay class="video"></video>
3:拍照后显示的图片
<img :src="downloadImgLink" alt="" ref="photosDownload"  class="photos-download">
3:点击拍照的按钮
<button class="operate-button" @click="btnTakePhotoClicked"> 
   <div class="round"></div>
</button>

四、方法

1:点击拍照
async btnTakePhotoClicked(){
            this._context2d=this.canvasCamera.getContext("2d");
            //如果已经拍照了就不能在点击拍照
            if(!this.photoEnabled) return
            // 将canvas画布设置和视频元素的大小一样
            this.canvasCamera.width=this.photoVideo.offsetWidth
            this.canvasCamera.height=this.photoVideo.offsetHeight
            // 截取和视频一样大小的图片保证图片没有变形
			this._context2d.drawImage(this.photoVideo,0,0,this.photoVideo.offsetWidth,this.photoVideo.offsetHeight )
			this.downloadImgLink =this.canvasCamera.toDataURL("image/png"); // 截取视频最后一帧
            this.photoEnabled=false
		},
2:下载拍摄照片
 //下载拍摄的照片
       async downloadImg(){
            //如果没有拍照点击下载无效
            if(this.downloadImgLink==='') return
            let downloadBase64= await this.composeImgs(this.photoImg, this.photosDownload);
            //下载base64格式图片需要使用a标签来创建
            let a = document.createElement("a");
            a.style.display = "none";
            a.download = 'christmas';
            a.href = downloadBase64;
            document.body.appendChild(a);
            a.click();
            // 下载完成可以点击拍照
            this.photoEnabled=true
            //下载完成清空上次拍照地址
            this.downloadImgLink=''
        },
3:将拍好的照片和相框合成一张图片,返回一个base64的图片地址
composeImgs(img1,img2){
            return new Promise((resolve, reject) => {
                //2,创建画笔
                const context = this.downloadCanvas.getContext('2d');
                //3,设置背景的宽高
                this.downloadCanvas.width = this.photoVideo.offsetWidth;
                this.downloadCanvas.height = this.photoVideo.offsetHeight;
                // 第一张图片传的是个链接 需要new Image 创建一个html元素
                // 如果本身就是一个html元素 那么可以直接调用 drawImage 方法进行绘制图片
                const myImage1 = new Image();
                myImage1.src = img1;
                myImage1.crossOrigin = 'Anonymous';// 跨域设置
                myImage1.onload = () => {
                    context.drawImage(img2, 0, 0,this.photoVideo.offsetWidth,this.photoVideo.offsetHeight); // 开始画第一张,拍的人物照片
                    context.drawImage(myImage1, 0, 0, this.photoVideo.offsetWidth,this.photoVideo.offsetHeight); // 开始画第二张,相框放到上面
                    const base64 = this.downloadCanvas.toDataURL('image/png'); //获取base64的图片流
                    resolve(base64); // 成功之后返回出去
                };
            })
        },

下面是完整代码

HTML部分

<template>
    <div style="padding: 12rem 0;">
        <div class="photograph">
            <div class="frame-box" @mouseenter="stopScrolling" @mouseleave="startScrolling">
                <div class="frame" ref="frameWrap">
                    <div class="item" v-for="(item, index) in frameList" :key="index" @click="frameBtn(index)">
                        <img :src="item" alt="" >
                    </div>
                </div>
            </div>
        </div>
        <div class="photo-frame" v-show="frameBox">
            <div class="photograph-frame">
                <img src="../cyberxmas-close.png" alt="" class="close" @click="close">
                <div class="photo">
                    <img :src="photoImg" alt="" class="photo-img">
                    <img :src="downloadImgLink" alt="" ref="photosDownload"  class="photos-download">
                    <video ref="photoVideo" autoplay class="video"></video>
                    <!-- 拍照canvas -->
                    <canvas  style="display: none;"  ref="canvasCamera" class="canvas"></canvas>
                </div>
                <div class="operate">
                    <div class="download text" @click="downloadImg">Download</div>
                    <button class="operate-button" @click="btnTakePhotoClicked"> 
                        <div class="round"></div>
                    </button>
                    <div class="download text" @click="retake">Retake</div>
                </div>
            </div>
            <!-- 合成图片canvas -->
            <canvas ref="downloadCanvas" style="display: none;"></canvas>
        </div>
    </div>
</template>

JS部分。注意图片按照自己的项目结构引入图片,navigator.mediaDevices.getUserMedia({})中video参数根据自己需求进行设置、调整

<script>
export default {
    data() {
        return {
            mobile:false,//是否是移动端
            frameBox:false, //拍照弹框
            frameList: [
                require('/img/frame-1.png'),
                require('/img/frame-2.png'),
                require('img/frame-4.png'),
                require('img/frame-5.png'),
                require('img/frame-6.png'),
                require('/img/frame-7.png'),
                require('/img/frame-8.png'),
                require('/img/frame-9.png'),
                require('/img/frame-10.png'),
                require('/img/frame-11.png'),
                require('/img/frame-12.png'),
                require('/img/frame-13.png'),
                require('/img/frame-14.png'),
                require('/img/frame-15.png'),
                require('/img/frame-16.png'),
            ],
            timer: null,
            direction: -1,
            photoImg:'/./img/frame-11.0.png?c0cda5dab250893f74a2783d10767d0',
            downloadImgLink:'',
            photoEnabled:true,//拍照按钮是否启用
        }
    },
    computed:{
        // 无缝滚动
        frameWrap(){
            return this.$refs.frameWrap
        },
        //拍照视频框
        photoVideo(){
            return this.$refs.photoVideo
        },
        //拍照canvas
        canvasCamera(){
            return this.$refs.canvasCamera
        },
        //合成图片cancas
        downloadCanvas(){
            return this.$refs.downloadCanvas
        },
        // 拍照后的图片
        photosDownload(){
            return this.$refs.photosDownload
        },
    },
    mounted() {
        import("public/js/flexible").then(() => {
            this.timer = setInterval(this.frameSlide, 20);
            this.mobile=$(window).width() <= 800
        })
    },
    beforeDestroy() {
        clearInterval(this.timer)
    },
    methods: {
        //点击拍照
        frameBtn(index){
            this.photoImg=this.frameList[index]
            this.camera()
        },
        //调用摄像头,绘制画布
        async camera(){
            if (!("mediaDevices" in navigator) || !("getUserMedia" in navigator.mediaDevices)) {
                alert("Media Capture API is not supported");
                return;
            }
            //将视频流放到video标签中显示拍摄画面
            await navigator.mediaDevices.getUserMedia({ video:{
                //设置图片宽高
                width:590,
                height:590,
                },audio:false }).then((stream)=>{
                this.photoVideo.srcObject=stream
                //显示弹框
			    this.frameBox=true
            }).catch(err=>{
                alert(err)
            })
		},
        async btnTakePhotoClicked(){
            this._context2d=this.canvasCamera.getContext("2d");
            //如果已经拍照了就不能在点击拍照
            if(!this.photoEnabled) return
            // 将canvas画布设置和视频元素的大小一样
            this.canvasCamera.width=this.photoVideo.offsetWidth
            this.canvasCamera.height=this.photoVideo.offsetHeight
            // 截取和视频一样大小的图片保证图片没有变形
			this._context2d.drawImage(this.photoVideo,0,0,this.photoVideo.offsetWidth,this.photoVideo.offsetHeight )
			this.downloadImgLink =this.canvasCamera.toDataURL("image/png"); // 截取视频最后一帧
            this.photoEnabled=false
		},
        //清空画布重新拍照
        retake(){
            this.downloadImgLink=''
            // 清空画布还能点击拍照
            this.photoEnabled=true
        },
        //下载拍摄的照片
       async downloadImg(){
            //如果没有拍照点击下载无效
            if(this.downloadImgLink==='') return
            let downloadBase64= await this.composeImgs(this.photoImg, this.photosDownload);
            //下载base64格式图片需要使用a标签来创建
            let a = document.createElement("a");
            a.style.display = "none";
            a.download = 'christmas';
            a.href = downloadBase64;
            document.body.appendChild(a);
            a.click();
            // 下载完成可以点击拍照
            this.photoEnabled=true
            //下载完成清空上次拍照地址
            this.downloadImgLink=''
        },
        // 合成图片
        composeImgs(img1,img2){
            return new Promise((resolve, reject) => {
                //2,创建画笔
                const context = this.downloadCanvas.getContext('2d');
                //3,设置背景的宽高
                this.downloadCanvas.width = this.photoVideo.offsetWidth;
                this.downloadCanvas.height = this.photoVideo.offsetHeight;
                // 第一张图片传的是个链接 需要new Image 创建一个html元素
                // 如果本身就是一个html元素 那么可以直接调用 drawImage 方法进行绘制图片
                const myImage1 = new Image();
                myImage1.src = img1;
                myImage1.crossOrigin = 'Anonymous';// 跨域设置
                myImage1.onload = () => {
                    context.drawImage(img2, 0, 0,this.photoVideo.offsetWidth,this.photoVideo.offsetHeight); // 开始画第一张,拍的人物照片
                    context.drawImage(myImage1, 0, 0, this.photoVideo.offsetWidth,this.photoVideo.offsetHeight); // 开始画第二张,相框放到上面
                    const base64 = this.downloadCanvas.toDataURL('image/png'); //获取base64的图片流
                    resolve(base64); // 成功之后返回出去
                };
            })
        },
        // 关闭拍照弹框
        close(){
            this.frameBox=false
            // 清空截图
            this.downloadImgLink=''
            // 启用拍照按钮
            this.photoEnabled=true
        },
        //相框无缝滚动
        frameSlide() {
            //让frameWrap的marginLeft不断变小   direction:滚动方向 -1是从右到左,1是从左到右  list数组   speed:滚动速度
            this.frameWrap.style.marginLeft = this.frameWrap.offsetLeft + this.direction * 1 + 'px';
            //判断当frameWrap滚动到一半时,从新拉回去,重0开始
            if (this.frameWrap.offsetLeft <= -this.frameWrap.offsetWidth / 2) {
                this.frameWrap.style.marginLeft = 0;
            }
            if (this.frameWrap.offsetLeft > 0) {
                this.frameWrap.style.marginLeft = -this.frameWrap.offsetWidth / 2 + 'px';
            }
        },
        // 鼠标移入停止滚动
        stopScrolling() {
            clearInterval(this.timer)
        },
        //鼠标移出开始滚动
        startScrolling() {
            this.timer = setInterval(this.frameSlide, 20);
        }
    }
}
</script>

CSS部分

<style scoped lang="scss">
.photograph {
    .title {
        display: block;
        margin: auto;
        height: 0.45rem;
        width: auto;
        margin-top: 0.87rem;
    }
    .frame-box {
        width: 13.39rem;
        margin: auto;
        margin-top: 0.53rem;
        height: 2.5rem;
        overflow: hidden;
        position: relative;
        .frame {
            display: flex;
            position: absolute;
            top: 0.15rem;
            z-index: 100;
            .item {
                width: 2rem;
                height: 2rem;
                background: #E2E2E2;
                border: 0.06rem solid #FFFFFF;
                opacity: 0.7;
                border-radius: 0.3rem;
                display: flex;
                align-items: center;
                justify-content: center;
                margin-right: 0.24rem;
                cursor: pointer;
                &:hover {
                    transform: scale(1.1);
                }
                img {
                    width: 1.9rem;
                    height: 1.9rem;
                    display: block;
                    border-radius: 0.3rem;
                }
            }
        }
    }
}
.photo-frame{
    position: fixed;
    width: 100vw;
    height: 100vh;
    top: 0;
    background: rgba(0,0,0,0.6);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 999;
    .photograph-frame{
        width: 8.73rem;
        height: 8.66rem;
        background: rgba(108, 84, 127, 0.92);
        border-radius: 0.02rem;
        position: relative;
        padding-top: 0.61rem;
        box-sizing: border-box;
        .close{
            position: absolute;
            right: 0.33rem;
            top: 0.19rem;
            width: 0.28rem;
            height: 0.28rem;
            cursor: pointer;
        }
        .photo{
            width: 5.9rem;
            height: 5.9rem;
            // background: #010308;
            border: 0.02rem solid rgba(255,255,255,0.36);
            border-radius: 0.02rem;
            margin: auto;
            position: relative;
            .photo-img,.photos-download{
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                z-index: 2;
            }
            .photos-download{
                width: 100%;
                z-index: 1;
            }
            .video{
                width: 100%;
                height: auto;
                position: absolute;
            }
            .canvas{
                position: absolute;
                left: -100%;
                z-index: 1;
            }
        }
        .operate{
            display: flex;
            align-items: center;
            justify-content: center;
            margin-top: 0.23rem;
            .text{
                width: 0.86rem;
                font-size: 0.19rem;
                color: #F4CCFB;
                text-shadow: 0px 0px 0.09rem #F25DFF;
                cursor: pointer;
            }
            .operate-button{
                width: 0.67rem;
                height: 0.67rem;
                border: 0.04rem solid #FFFFFF;
                border-radius: 50%;
                background-color: transparent;
                margin: 0 0.32rem;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                .round{
                    width: 0.4rem;
                    height: 0.4rem;
                    background-color: #FFFFFF;
                    border-radius: 50%;
                }
            }
        }
    }
}
</style>

最终下载下来的图片效果

最近更新

  1. TCP协议是安全的吗?

    2023-12-14 19:38:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-14 19:38:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-14 19:38:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-14 19:38:01       20 阅读

热门阅读

  1. 软件开发经常出现的bug原因有哪些

    2023-12-14 19:38:01       38 阅读
  2. CDN加速:社会服务的必备利器

    2023-12-14 19:38:01       36 阅读
  3. LeetCode 2697. 字典序最小回文串

    2023-12-14 19:38:01       44 阅读
  4. leetcode 最大和的连续子数组 C语言

    2023-12-14 19:38:01       31 阅读
  5. 敏捷开发项目管理流程及scrum工具

    2023-12-14 19:38:01       38 阅读
  6. K8S(七)—污点、容忍

    2023-12-14 19:38:01       41 阅读
  7. k8s-Pod

    k8s-Pod

    2023-12-14 19:38:01      32 阅读
  8. hive客户机执行sql脚本无法显示表头

    2023-12-14 19:38:01       37 阅读