vue实现微信对话
因为项目中需要实现仿微信对话功能,于是抽空实现了下,主要是h5的canvas的把图片和文字绘制到画布上
原文来自我的个人博客:http://lvhww.com/index.php/archives/6/
1.首先准备图片资源:微信头部导航,对话框,二维码等
2.API接口
3,效果图
4,代码DetailWechat.vue
<template> <div class="s-all"> <v-header :name="name"></v-header> <!-- 人物列表 --> <div class="s-content active"> <div class="s-recommend"> <span class="span-1">荐</span> <span>推荐人物</span></div> <div class="s-recommend-people"> <ul> <li v-for="item in recommendList"> <div class="s-img"> <img @click="drawResult(item.name,item.nickname,item._id,$event)" src="../assets/images/e217d58ec3d03b1ab5df0d92589a04bb.jpg"/> <!--:src="item.image.replace('/game/static/images/','')"/>--> </div> <div class="s-title" v-text="item.name"> </div> </li> </ul> </div> <div class="s-category"> <span class="span-2">类</span> <span>分类</span> </div> <div class="s-category-a"> <a class="s-bt" @click="getData(Aname.name)" v-for="Aname in Alist">{{Aname.name}}</a> </div> <div class="s-category-list"> <ul> <li v-for="item in itemList"> <a class="s-a-1" @click="drawResult(item.name,item.name,item._id)" :data-alt="item.name" :data-nickname="item.name" :data-num="item._id" name="s-a-1"> <div class="s-a-img"> <img v-lazy="item.image" class="star-icon" id="star-icon"/> </div> <div class="s-a-title"> <div class="s-a-title-name" v-text="item.name"></div> <div class="s-a-title-play"> <span class="m-icon icon-star"></span> <span class="m-icon icon-star"></span> <span class="m-icon icon-star"></span> <span class="m-icon icon-star"></span> <span class="m-icon icon-star"></span> <span class="s-people">123456人在玩</span> </div> </div> </a> <a class="s-a-2" @click="drawResult(item.name,item.name,item._id,$event)" :data-alt="item.name" :data-nickname="item.name" :data-num="item._id"> 开始<span class="icon-more"></span> </a> </li> </ul> </div> </div> <!-- 正在制作 --> <div class="box" id="page2"> <img id="loading" class="loading" src="https://h5.tangdaoya.com/game/static/loading.png"/> <p id="loading-text" class="loading-text">正在生成</p> </div> <!-- 制作结果 --> <div class="box" id="page3"> <div class="common-result-btnbox"> <div class="commonbtn-playAgain btn-back" @click="changeContent()" v-if="ShowAgain"> 换个文案试试 </div> <div id="btn-share" class="commonbtn-share">立即去整人</div> </div> <div class="resultimgbox"> <img id="result" class="result" src=""/> </div> </div> <!-- 返回制作 --> <div class="box" id="page4"> <div class="butbox common-result-btnbox"> <div id="backtohomgpage" class="commonbtn-share">返回制作</div> </div> </div> <div class="" style="display: block"> <img src="../assets/images/header.jpg" id="headerImg" alt=""> <img src="../assets/images/footer.jpg" id="footerImg" alt=""> <img src="../assets/images/qr.png" id="codeImg" alt=""> <img src="../assets/images/8fe39669bff9423bb56c5586f5fa70d1.jpg" id="head2Img" alt=""> </div> <img src="" id="targetImg" alt=""> </div> </template> <script> import Vue from 'vue' import api from '../fetch/api.js' import com from '../assets/js/common' import axios from 'axios'; import VueLazyload from 'vue-lazyload' import {Toast} from 'mint-ui' import VHeader from '@/components/Header.vue' var headImg = "" || "https://h5.tangdaoya.com/zb/static/image/cover/8fe39669bff9423bb56c5586f5fa70d1.jpg"; var manifest = [{ src: "https://h5.tangdaoya.com/game/static/header.jpg", id: "header" }, { src: "https://h5.tangdaoya.com/game/static/footer.jpg", id: "footer" }, { src: headImg, id: "head2" }]; var headerEle; var footerEle; var codeEle; var head2Ele; var starImgObg; var chatContent, template; var starName = "张信哲", starNickName = "張信哲JeffChang", starID = 1, starImg; var cW = 640, cHeigth = 1138, headL = 18, headW = 63, headR = cW - headL - headW, padding = 20, fontSize = 23, lingheight = 30, imgTextSpace = 20, starTextL = headL + headW + imgTextSpace - 3, starTextR = headR - imgTextSpace, maxTextW = 405, gY = 0, verticlaSpace = 34, textRadius = 6; CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); return this; } export default { components: { VHeader }, data() { return { name: '', itemList: [], recommendList: [], Alist: [{ name: '影视大咖' }, { name: '女明星' }, { name: '男明星' }, { name: '商业大亨' }], starName: '', starNickName: '', starID: '', starImgObg: '', chatContent: '', template: '', ShowAgain: false } }, created() { this.getData(); }, mounted() { let self = this; headerEle = document.getElementById('headerImg'); footerEle = document.getElementById('footerImg'); codeEle = document.getElementById('codeImg'); head2Ele = document.getElementById('head2Img'); CanvasRenderingContext2D.prototype.drawStarText = function (x, y, r, text, translate) { self.drawStarImg(this, y); console.log('star') this.font = "24px Helvetica"; var hastrans = false; var wordCnt = com.isChineseChar(text) ? 32 : 33; console.log(wordCnt) var textArr = self.getText2Arr(this, text, wordCnt); var h = headW + lingheight * (textArr.length - 1); var w = (textArr.length > 1) ? maxTextW : (this.measureText(textArr[0]).width + 2 * padding); if (translate) { hastrans = true; var transArr = self.getText2Arr(this, translate, 32); h = h + lingheight * (transArr.length) + 48; if (transArr.length > 1) { w = maxTextW; } } gY = y + h + verticlaSpace; if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.save(); this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.lineTo(x, y + 31 + 7); this.lineTo(x - 10, y + 31); this.lineTo(x, y + 31 - 7); this.arcTo(x, y, x + w, y, r); this.closePath(); this.strokeStyle = '#d3d3d3'; this.lineWidth = 2; this.stroke(); this.fillStyle = "#fff"; this.fill(); this.textBaseline = 'top'; this.textAlign = 'left'; this.fillStyle = '#000'; self.writeTextArr(this, textArr, lingheight, x + padding, y + padding); //writeMulLineText(this, text, 32, 30, x + padding, y+padding); //this.fillText(text, x + padding, y+padding); if (hastrans) { this.beginPath(); var lineY = y + padding * 2 + textArr.length * lingheight; this.moveTo(x + padding, lineY); this.lineTo(x + w - padding, lineY); this.strokeStyle = "#ddd"; this.stroke(); self.writeTextArr(this, transArr, lingheight, x + padding, lineY + 20); gY = gY - verticlaSpace; this.transFlag(x, gY + 8, textRadius); } this.restore(); return this; } CanvasRenderingContext2D.prototype.drawSelfText = function (x, y, r, text) { self.drawSelfImg(this, y); this.font = "24px Helvetica"; // Arial var wordCnt = com.isChineseChar(text) ? 32 : 33; var textArr = self.getText2Arr(this, text, wordCnt); var h = headW + lingheight * (textArr.length - 1); var w = (textArr.length > 1) ? maxTextW : (this.measureText(textArr[0]).width + 2 * padding); x = x - w; gY = y + h + verticlaSpace; if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.save(); this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.lineTo(x + w, y + 31 + 7); this.lineTo(x + w + 10, y + 31); this.lineTo(x + w, y + 31 - 7); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); this.strokeStyle = '#8bdf49'; this.lineWidth = 2; this.stroke(); this.fillStyle = "#a0e75a"; this.fill(); this.textBaseline = 'top'; this.textAlign = 'left'; this.fillStyle = '#000'; self.writeTextArr(this, textArr, lingheight, x + padding, y + padding); //writeMulLineText(this, text, 32, 30, x + padding, y+padding); //this.fillText(text, x + padding, y+padding); this.restore(); return this; } CanvasRenderingContext2D.prototype.drawHelloText = function (x, y, r, text) { this.save(); this.font = "18px Helvetica"; var h = 32; var w = this.measureText(text).width + 2 * padding; x = x - w / 2; gY = y + h + 22; if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); this.fillStyle = "#cecece"; this.fill(); this.textBaseline = 'middle'; this.textAlign = 'center'; this.fillStyle = '#fff'; this.fillText(text, cW / 2, y + h / 2); this.restore(); return this; } CanvasRenderingContext2D.prototype.transFlag = function (x, y, r, text) { this.save(); this.font = "18px Helvetica"; var h = 32; text = '√ 已翻译'; var transpading = 10; var w = this.measureText(text).width + 2 * transpading; gY = y + h + verticlaSpace; if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); this.fillStyle = "#cecece"; this.fill(); this.textBaseline = 'middle'; this.textAlign = 'left'; this.fillStyle = '#fff'; this.fillText(text, x + transpading, y + h / 2); this.restore(); return this; } CanvasRenderingContext2D.prototype.drawQrcode = function (img, x, y) { self.drawStarImg(this, y); var h = 200 + padding; var w = 200 + padding; var r = textRadius; if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.save(); this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.lineTo(x, y + 31 + 7); this.lineTo(x - 10, y + 31); this.lineTo(x, y + 31 - 7); this.arcTo(x, y, x + w, y, r); this.closePath(); this.strokeStyle = '#d3d3d3'; this.lineWidth = 2; this.stroke(); this.fillStyle = "#fff"; this.fill(); this.drawImage(img, x + 10, y + 10, 200, 200) gY = y + h + verticlaSpace; } }, methods: { getData(ev) { let self = this; // 默认页面加载的数据 if (ev == undefined) { self.$http.get('https://h5.tangdaoya.com/game/wechat/getstarbytype/影视大咖') .then(res => { let result = res.data.list; // self.itemList = result; self.recommendList = result; }).catch(error => { Toast('网络出现错误,请稍后再试'); }); } else { // 传递参数请求接口 self.$http.get('https://h5.tangdaoya.com/game/wechat/getstarbytype/' + ev) .then(res => { console.log(res.data.list); let result = res.data.list; self.itemList = result; }).catch(error => { Toast('网络出现错误,请稍后再试'); }); } }, drawResult(a, b, c, e) { let self = this; self.starName = a; self.starNickName = b; self.starID = c; starImgObg = e.target; Toast('正在生成中~'); self.getChatContent(); }, gameStart() { let self = this; var canvas = document.createElement('canvas'); canvas.width = cW; canvas.height = cHeigth; var context = canvas.getContext('2d'); context.fillStyle = '#ebebeb'; context.fillRect(0, 0, cW, cHeigth); context.drawImage(headerEle, 0, 0); self.writeHeader(context, starName); gY = 115; if (template == 1) { context.drawHelloText(cW / 2, gY, textRadius, com.getTime(true)); } else { context.drawHelloText(cW / 2, gY, textRadius, com.getDateTime()); } for (var i = 0; i < chatContent.length; i++) { var contentTemp = chatContent[i]['content']; if (com.is_weixn()) { contentTemp = contentTemp.replace(/xxx/ig, nickName); } else { contentTemp = contentTemp.replace(/是xxx吗?/ig, ''); } if (chatContent[i] && chatContent[i]['star']) { if (chatContent[i]['isImg']) { // context.drawQrcode(codeEle, starTextL, gY); context.drawQrcode(codeEle, starTextL, gY); } else { context.drawStarText(starTextL, gY, textRadius, contentTemp, chatContent[i]['translate']); if (i == 0 && template == 2) { self.drawHello(context, starNickName); gY = gY + 12; } } } else { context.drawSelfText(starTextR, gY, textRadius, contentTemp); } } // context.drawImage(footerEle, 0, cHeigth - 78, 640, 78); context.drawImage(footerEle, 0, cHeigth - 78, 640, 78); console.log('canvas'+canvas); console.log(canvas); console.log(canvas.toDataURL("image/png", 0.6)); self.saveImage(canvas.toDataURL("image/png", 0.6)) }, saveImage(data) { $('#targetImg').attr('src', data); }, getChatContent() { let self = this; var server = "https://h5.tangdaoya.com/game/wechat/getChatContent/" + 2;//this.starID self.$http.get(server).then(res => { let result = res.data; this.chatContent = result.chatContents; chatContent = this.chatContent; this.template = result['template']; if (result['changeContent'] == 0) { //Selector('.commonbtn-playAgain').style.display = "none"; this.ShowAgain = false; } else if (result['changeContent'] == 1) { this.ShowAgain = true; } self.gameStart(); }).catch(error => { //Toast('网络出现错误,请稍后再试'); }); }, changeContent() { $('#result').attr('src', ''); $('#page2').removeClass('active'); $('#page3').addClass('active'); }, drawStarImg(ctx, y) { ctx.drawImage(starImgObg, headL, y, headW, headW); }, writeHeader(ctx, name) { var y = 67; ctx.save(); ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillStyle = '#fff'; ctx.font = "19px Arial"; ctx.fillText(com.getTime(), cW / 2, 18); ctx.font = "29px Arial"; ctx.fillText(name, cW / 2, y); ctx.restore(); }, drawSelfImg(ctx, y) { //ctx.drawImage(selfImgObg, headR, y, headW, headW) ; // ctx.drawImage(head2Ele, headR, y, headW, headW); ctx.drawImage(head2Ele, headR, y, headW, headW); }, getText2Arr(ctx, text, rw) { var textArr = []; for (var i = 0; com.getTrueLength(text) > 0; i++) { var tl = com.cutString(text, rw); if (ctx.measureText(text.substr(0, tl)).width < 350) tl = tl + 1; for (var j = 0; j < tl; j++) { var temp = text.substr(tl, 1); if (temp == " " || temp.charCodeAt(0) > 128 || temp == "") { break; } else { tl = tl - 1; } } //ctx.fillText(text.substr(0, tl).replace(/^\s+|\s+$/, ""), offsetX , i * lineheight + offsetY); textArr.push(text.substr(0, tl)); text = text.substr(tl); } return textArr; }, writeTextArr(ctx, textArr, lineheight, offsetX, offsetY) { for (var i = 0; i < textArr.length; i++) { ctx.fillText(textArr[i].replace(/^\s+|\s+$/, ""), offsetX, i * lineheight + offsetY); } } } } </script> <style lang="less" rel="stylesheet/less" scoped> .s-content { margin-top: 50px; height: 1000px; } .s-recommend { height: 30px; line-height: 2em; border-bottom: 1px solid #F0F0F7; .span-1 { background-color: #FF4852; color: white; margin-left: 10px; } } .s-recommend-people { width: 100%; height: 100px; margin-top: 5px; border-bottom: 3px solid #F0F0F7; ul { width: 100%; height: 100px; margin: 0 auto; li { position: relative; display: inline-block; list-style: none; width: 25%; height: 80px; margin-top: 10px; margin-left: 6%; .s-img { width: 50px; height: 50px; margin: 0 auto; img { width: 50px; height: 50px; border-radius: 10px; } } .s-title { width: 50px; height: 20px; border: 1px solid balck; margin: 0 auto; text-align: center; margin-top: 5px; font-size: 0.8em; } } } } .s-category { height: 30px; line-height: 2em; border-bottom: 1px solid #F0F0F7; .span-2 { background-color: #139F00; color: white; margin-left: 10px; } } .s-category-a { height: 50px; margin-top: 5px; a { border: 1px solid #9b9b9b; display: inline-block; border-radius: 10px; padding: .15rem 0.31rem; display: inline-block; margin-top: 12px; height: 25px; line-height: 1.6em; margin-left: 10px; } } .s-category-list { margin-top: 10px; height: 700px; ul { width: 100%; height: 100%; li { border-bottom: 1px solid #F0F0F7; width: 100%; height: 80px; margin-top: 10px; .s-a-1 { float: left; width: 78%; height: 80px; .s-a-img { float: left; width: 60px; height: 60px; background-color: gray; margin-top: 10px; margin-left: 10px; border-radius: 10px; img { width: 60px; height: 60px; border-radius: 10px; } } .s-a-title { float: left; height: 60px; margin-top: 10px; margin-left: 5px; .s-a-title-name { margin-top: 10px; color: black; font-size: 0.8em; } .s-a-title-play { margin-top: 10px; .s-people { font-size: 0.8em; } .icon-star { font-size: 0.9em; } } } } .s-a-2 { float: right; width: 60px; height: 27px; border: 1px solid #EFB31D; margin-top: 25px; margin-right: 20px; border-radius: 5px; line-height: 1.8em; text-align: center; color: #EFB31D; .icon-more { display: inline-block; color: #EFB31D; } } } } } /* 制作页样式*/ .box { display: none; .loading { position: relative; width: 75px; margin-top: 40%; } .loading-text { position: relative; text-align: center; color: #FF4303; margin-top: 20px auto; font-family: "微软雅黑"; letter-spacing: 1px; } } .commonbtn-share { display: inline-block; margin: 0.5%; padding: 0.5em 0em; font-size: 16px; color: white; background-color: #FF4303; border-radius: 40px; width: 35%; box-sizing: border-box; -webkit-box-sizing: border-box; } .active .loading { -webkit-animation: roting 1s linner infinite; } </style>