在前端生成H5二维码海报
海报图片生成前后端都能实现,个人喜欢在前端生成,主要是前端可以用html+css灵活的实现非常漂亮海报样式,后端比较麻烦,且前端也更便于调试,对于熟悉前端代码的小伙伴来说再好不过。
以下是在vue项目中的实现,非vue前端同理。
思路及步骤:
1. 用html实现海报效果
制作海报模板图,用js二维码库生成二维码,用CSS的绝对定位实现二维码处于模板图上一层
2. 将html转canvas
用到第三方库html2canvas,npm install html2canvas
3. 将canvas转图片
用到第三方库canvas2image,这个库是国人开发的,不过有点坑,它的源代码就是一个函数,并没有实现模块化就提交到了npmjs.com官网,导致install后也无法正常引入到项目中,比较的不规范。我是直接复制他的源代码到项目中使用。
html部分
<el-button type="primary" @click="toShare()" size="small" icon="el-icon-share">生成海报</el-button> <!-- 海报生成html模板,为了不让海报显示在页面上,使用绝对定位,且让其漂移到页面外很远的地方 --> <div id="posterHtml" class="posterHtml" style="width:390px;height:693px;position:absolute;top:-10000px;left:-10000px;"> <img :src="'https://oss-exam.chanjet.com/ckt_html/poster_template/ec_poster.jpg'" crossorigin="anonymous" style="width:inherit;height:inherit;position:absolute;z-index: 10;" > <div style="width:inherit;height:inherit;position:absolute;z-index: 20;display: flex;flex-direction:column;justify-content: flex-start;align-items: center;"> <div style="font-size:18px;position:absolute;top:30px;width:100%;text-align: left;color:#FFFFCC;text-shadow: 0 8px 10px #FFFFCC;"> <div style="margin-left: 20px;font-family: 'Arial Black';">我们是【{{teamName}}】团队</div> <div style="margin-left: 20px;">正在参加“XX杯”XXXXXX大赛</div> <div style="margin-left: 20px;">请为我们的作品投一票吧~</div> </div> <!-- 二维码 --> <img id="posterQRCode" crossorigin="anonymous" style="width:200px;height:200px;position:absolute;top:420px;" /> </div> </div> <!-- 分享海报生成后的图片弹窗 --> <el-dialog width="350px" title="作品分享海报" v-if="posterVisible" :visible.sync="posterVisible" append-to-body> <div style="margin-top:-30px;display: flex;flex-direction:column;justify-content:center;align-items: center;"> <div id="myPosterContainer" style="width:100%;display: flex;justify-content: center;align-items: center;"></div> <div style="line-height: 30px;">长按图片保存或转发</div> </div> </el-dialog>
js部分
method: { toShare() { const text = this.getHost() + '/#/2023ec/videoVote?videoInfoId=' + this.videoInfoId // 生成二维码 QRCode.toDataURL(text, { width: 200, height: 200, src: '' }).then(url => { // <div style="margin:20px 0 20px 0"><img id="qrcode" :src="shortVideoQRCodeURL"/></div> document.querySelector('#posterQRCode').src = url this.createPoster() this.posterVisible = true }).catch(err => { console.error(err) }) }, createPoster() { // 生成海报 const vm = this; const domObj = document.getElementById('posterHtml'); var width = parseInt(domObj.style.width); var height = parseInt(domObj.style.height); var canvas = document.createElement('canvas'); var scale = 1; canvas.width = width * scale; canvas.height = height * scale; canvas.getContext('2d').scale(scale, scale); var opts = { scale: scale, canvas: canvas, width: width, height: height, useCORS: true, allowTaint: false, logging: false, letterRendering: true }; console.info('准备开始html2canvas') html2canvas(domObj, opts).then(function(canvas) { var context = canvas.getContext('2d'); // 重要 关闭抗锯齿 context.mozImageSmoothingEnabled = false; context.webkitImageSmoothingEnabled = false; context.msImageSmoothingEnabled = false; context.imageSmoothingEnabled = false; console.info('准备开始Canvas2Image') console.info('canvas2image:', Canvas2Image) var img = Canvas2Image.convertToImage( canvas, canvas.width, canvas.height ); vm.postshow = false; vm.postcode = false; // img.style.width = canvas.width / 2 + 'px'; img.style.width = '330px'; // img.style.height = canvas.height / 2 + 'px'; document.getElementById('myPosterContainer').appendChild(img); }); }, }
canvas2image源代码
/** * covert canvas to image * and save the image file */ var Canvas2Image = function () { // check if support sth. var $support = function () { var canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'); return { canvas: !!ctx, imageData: !!ctx.getImageData, dataURL: !!canvas.toDataURL, btoa: !!window.btoa }; }(); var downloadMime = 'image/octet-stream'; function scaleCanvas (canvas, width, height) { var w = canvas.width, h = canvas.height; if (width == undefined) { width = w; } if (height == undefined) { height = h; } var retCanvas = document.createElement('canvas'); var retCtx = retCanvas.getContext('2d'); retCanvas.width = width; retCanvas.height = height; retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height); return retCanvas; } function getDataURL (canvas, type, width, height) { canvas = scaleCanvas(canvas, width, height); return canvas.toDataURL(type); } function saveFile (strData) { document.location.href = strData; } function genImage(strData) { var img = document.createElement('img'); img.src = strData; return img; } function fixType (type) { type = type.toLowerCase().replace(/jpg/i, 'jpeg'); var r = type.match(/png|jpeg|bmp|gif/)[0]; return 'image/' + r; } function encodeData (data) { if (!window.btoa) { throw 'btoa undefined' } var str = ''; if (typeof data == 'string') { str = data; } else { for (var i = 0; i < data.length; i ++) { str += String.fromCharCode(data[i]); } } return btoa(str); } function getImageData (canvas) { var w = canvas.width, h = canvas.height; return canvas.getContext('2d').getImageData(0, 0, w, h); } function makeURI (strData, type) { return 'data:' + type + ';base64,' + strData; } /** * create bitmap image * 按照规则生成图片响应头和响应体 */ var genBitmapImage = function (oData) { // // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx // var biWidth = oData.width; var biHeight = oData.height; var biSizeImage = biWidth * biHeight * 3; var bfSize = biSizeImage + 54; // total header size = 54 bytes // // typedef struct tagBITMAPFILEHEADER { // WORD bfType; // DWORD bfSize; // WORD bfReserved1; // WORD bfReserved2; // DWORD bfOffBits; // } BITMAPFILEHEADER; // var BITMAPFILEHEADER = [ // WORD bfType -- The file type signature; must be "BM" 0x42, 0x4D, // DWORD bfSize -- The size, in bytes, of the bitmap file bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff, // WORD bfReserved1 -- Reserved; must be zero 0, 0, // WORD bfReserved2 -- Reserved; must be zero 0, 0, // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits. 54, 0, 0, 0 ]; // // typedef struct tagBITMAPINFOHEADER { // DWORD biSize; // LONG biWidth; // LONG biHeight; // WORD biPlanes; // WORD biBitCount; // DWORD biCompression; // DWORD biSizeImage; // LONG biXPelsPerMeter; // LONG biYPelsPerMeter; // DWORD biClrUsed; // DWORD biClrImportant; // } BITMAPINFOHEADER, *PBITMAPINFOHEADER; // var BITMAPINFOHEADER = [ // DWORD biSize -- The number of bytes required by the structure 40, 0, 0, 0, // LONG biWidth -- The width of the bitmap, in pixels biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff, // LONG biHeight -- The height of the bitmap, in pixels biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff, // WORD biPlanes -- The number of planes for the target device. This value must be set to 1 1, 0, // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap // has a maximum of 2^24 colors (16777216, Truecolor) 24, 0, // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed 0, 0, 0, 0, // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff, // LONG biXPelsPerMeter, unused 0,0,0,0, // LONG biYPelsPerMeter, unused 0,0,0,0, // DWORD biClrUsed, the number of color indexes of palette, unused 0,0,0,0, // DWORD biClrImportant, unused 0,0,0,0 ]; var iPadding = (4 - ((biWidth * 3) % 4)) % 4; var aImgData = oData.data; var strPixelData = ''; var biWidth4 = biWidth<<2; var y = biHeight; var fromCharCode = String.fromCharCode; do { var iOffsetY = biWidth4*(y-1); var strPixelRow = ''; for (var x = 0; x < biWidth; x++) { var iOffsetX = x<<2; strPixelRow += fromCharCode(aImgData[iOffsetY+iOffsetX+2]) + fromCharCode(aImgData[iOffsetY+iOffsetX+1]) + fromCharCode(aImgData[iOffsetY+iOffsetX]); } for (var c = 0; c < iPadding; c++) { strPixelRow += String.fromCharCode(0); } strPixelData += strPixelRow; } while (--y); var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData); return strEncoded; }; /** * saveAsImage * @param canvasElement * @param {String} image type * @param {Number} [optional] png width * @param {Number} [optional] png height */ var saveAsImage = function (canvas, width, height, type) { if ($support.canvas && $support.dataURL) { if (typeof canvas == "string") { canvas = document.getElementById(canvas); } if (type == undefined) { type = 'png'; } type = fixType(type); if (/bmp/.test(type)) { var data = getImageData(scaleCanvas(canvas, width, height)); var strData = genBitmapImage(data); saveFile(makeURI(strData, downloadMime)); } else { var strData = getDataURL(canvas, type, width, height); saveFile(strData.replace(type, downloadMime)); } } }; var convertToImage = function (canvas, width, height, type) { if ($support.canvas && $support.dataURL) { if (typeof canvas == "string") { canvas = document.getElementById(canvas); } if (type == undefined) { type = 'png'; } type = fixType(type); if (/bmp/.test(type)) { var data = getImageData(scaleCanvas(canvas, width, height)); var strData = genBitmapImage(data); return genImage(makeURI(strData, 'image/bmp')); } else { var strData = getDataURL(canvas, type, width, height); return genImage(strData); } } }; return { saveAsImage: saveAsImage, saveAsPNG: function (canvas, width, height) { return saveAsImage(canvas, width, height, 'png'); }, saveAsJPEG: function (canvas, width, height) { return saveAsImage(canvas, width, height, 'jpeg'); }, saveAsGIF: function (canvas, width, height) { return saveAsImage(canvas, width, height, 'gif'); }, saveAsBMP: function (canvas, width, height) { return saveAsImage(canvas, width, height, 'bmp'); }, convertToImage: convertToImage, convertToPNG: function (canvas, width, height) { return convertToImage(canvas, width, height, 'png'); }, convertToJPEG: function (canvas, width, height) { return convertToImage(canvas, width, height, 'jpeg'); }, convertToGIF: function (canvas, width, height) { return convertToImage(canvas, width, height, 'gif'); }, convertToBMP: function (canvas, width, height) { return convertToImage(canvas, width, height, 'bmp'); } }; }(); export default Canvas2Image;