| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- <template>
- <view class="container">
- <canvas canvas-id="canvasid" class="dt-canvas" :class="debug ? 'debug' : 'pro'" :style="'width:'+pxWidth+'px;height:'+pxHeight+'px;'"></canvas>
- </view>
- </template>
- <script>
- export default {
- data() {
- return {
- ctx: {},
- factor: 0,
- pxHeight: 0,
- pxWidth: 0,
- };
- },
- methods: {
- /**
- * 计算画布的高度
- * @param {*} config
- */
- getHeight(config) {
- const getTextHeight = text => {
- let fontHeight = text.lineHeight || text.fontSize;
- let height = 0;
- if (text.baseLine === "top") {
- height = fontHeight;
- } else if (text.baseLine === "middle") {
- height = fontHeight / 2;
- } else {
- height = 0;
- }
- return height;
- };
- const heightArr = [];
- (config.blocks || []).forEach(item => {
- heightArr.push(item.y + item.height);
- });
- (config.texts || []).forEach(item => {
- let height;
- if (Object.prototype.toString.call(item.text) === "[object Array]") {
- item.text.forEach(i => {
- height = getTextHeight({ ...i, baseLine: item.baseLine });
- heightArr.push(item.y + height);
- });
- } else {
- height = getTextHeight(item);
- heightArr.push(item.y + height);
- }
- });
- (config.images || []).forEach(item => {
- heightArr.push(item.y + item.height);
- });
- (config.lines || []).forEach(item => {
- heightArr.push(item.startY);
- heightArr.push(item.endY);
- });
- const sortRes = heightArr.sort((a, b) => b - a);
- let canvasHeight = 0;
- if (sortRes.length > 0) {
- canvasHeight = sortRes[0];
- }
- if (config.height < canvasHeight || !config.height) {
- return canvasHeight;
- } else {
- return config.height;
- }
- },
- create(config) {
- this.ctx = uni.createCanvasContext("canvasid", this);
- const height = this.getHeight(config);
- this.initCanvas(config.width, height, config.debug)
- .then(() => {
- // 设置画布底色
- if (config.backgroundColor) {
- this.ctx.save();
- this.ctx.setFillStyle(config.backgroundColor);
- this.ctx.fillRect(
- 0,
- 0,
- this.toPx(config.width),
- this.toPx(height)
- );
- this.ctx.restore();
- }
- const { texts = [], images = [], blocks = [], lines = [] } = config;
- const queue = this.drawArr
- .concat(
- texts.map(item => {
- item.type = "text";
- item.zIndex = item.zIndex || 0;
- return item;
- })
- )
- .concat(
- blocks.map(item => {
- item.type = "block";
- item.zIndex = item.zIndex || 0;
- return item;
- })
- )
- .concat(
- lines.map(item => {
- item.type = "line";
- item.zIndex = item.zIndex || 0;
- return item;
- })
- );
- // 按照顺序排序
- queue.sort((a, b) => a.zIndex - b.zIndex);
- queue.forEach(item => {
- if (item.type === "image") {
- this.drawImage(item);
- } else if (item.type === "text") {
- this.drawText(item);
- } else if (item.type === "block") {
- this.drawBlock(item);
- } else if (item.type === "line") {
- this.drawLine(item);
- }
- });
- const res = uni.getSystemInfoSync();
- const platform = res.platform;
- let time = 0;
- if (platform === "android") {
- // 在安卓平台,经测试发现如果海报过于复杂在转换时需要做延时,要不然样式会错乱
- time = 300;
- }
- this.ctx.draw(false, () => {
- setTimeout(() => {
- uni.canvasToTempFilePath(
- {
- canvasId: "canvasid",
- success: res => {
- this.$emit("success", res.tempFilePath);
- },
- fail: err => {
- this.$emit("fail", err);
- }
- },
- this
- );
- }, time);
- });
- })
- .catch(err => {
- uni.showToast({ icon: "none", title: err.errMsg || "生成失败" });
- console.error(err);
- });
- },
- /**
- * main
- -----------------------*/
- /**
- * 渲染块
- * @param {Object} params
- */
- drawBlock({ text, width = 0, height, x, y, paddingLeft = 0, paddingRight = 0, borderWidth, backgroundColor, borderColor, borderRadius = 0, opacity = 1 }) {
- // 判断是否块内有文字
- let blockWidth = 0; // 块的宽度
- let textX = 0;
- let textY = 0;
- if (typeof text !== 'undefined') {
- // 如果有文字并且块的宽度小于文字宽度,块的宽度为 文字的宽度 + 内边距
- const textWidth = this._getTextWidth(typeof text.text === 'string' ? text : text.text);
- blockWidth = textWidth > width ? textWidth : width;
- blockWidth += paddingLeft + paddingLeft;
- x=x-blockWidth/2
- const { textAlign = 'left', text: textCon } = text;
- textY = height/2+ y; // 文字的y轴坐标在块中线
- if (textAlign === 'left') {
- // 如果是右对齐,那x轴在块的最左边
- textX = x + paddingLeft;
- } else if (textAlign === 'center') {
- textX = blockWidth / 2 + x;
- } else {
- textX = x + blockWidth - paddingRight;
- }
- } else {
- blockWidth = width;
- }
- if (backgroundColor) {
- // 画面
- this.ctx.save();
- this.ctx.setGlobalAlpha(opacity);
- this.ctx.setFillStyle(backgroundColor);
- if (borderRadius > 0) {
- // 画圆角矩形
- this._drawRadiusRect(x, y, blockWidth, height, borderRadius);
- this.ctx.fill();
- } else {
- this.ctx.fillRect(this.toPx(x), this.toPx(y), this.toPx(blockWidth), this.toPx(height));
- }
- this.ctx.restore();
- }
- if (borderWidth) {
- // 画线
- this.ctx.save();
- this.ctx.setGlobalAlpha(opacity);
- this.ctx.setStrokeStyle(borderColor);
- this.ctx.setLineWidth(this.toPx(borderWidth));
- if (borderRadius > 0) {
- // 画圆角矩形边框
- this._drawRadiusRect(x, y, blockWidth, height, borderRadius);
- this.ctx.stroke();
- } else {
- this.ctx.strokeRect(this.toPx(x), this.toPx(y), this.toPx(blockWidth), this.toPx(height));
- }
- this.ctx.restore();
- }
- if (text) {
- this.drawText(Object.assign(text, { x: textX, y: textY }))
- }
- },
- /**
- * 渲染文字
- * @param {Object} params
- */
- drawText(params) {
- const { x, y, fontSize, color, baseLine, textAlign, text, opacity = 1, width, lineNum, lineHeight } = params;
- if (Object.prototype.toString.call(text) === '[object Array]') {
- let preText = { x, y, baseLine };
- text.forEach(item => {
- preText.x += item.marginLeft || 0;
- const textWidth = this._drawSingleText(Object.assign(item, {
- ...preText,
- }));
- preText.x += textWidth + (item.marginRight || 0); // 下一段字的x轴为上一段字x + 上一段字宽度
- })
- } else {
- this._drawSingleText(params);
- }
- },
- /**
- * 渲染图片
- */
- drawImage(data) {
- const { imgPath, x, y, w, h, sx, sy, sw, sh, borderRadius = 0, borderWidth = 0, borderColor } = data;
- this.ctx.save();
- if (borderRadius > 0) {
- this.ctx.setFillStyle("#FFF");
- this._drawRadiusRect(x, y, w, h, borderRadius);
- this.ctx.fill();
- this.ctx.clip();
- this.ctx.drawImage(imgPath, this.toPx(sx), this.toPx(sy), this.toPx(sw), this.toPx(sh), this.toPx(x), this.toPx(y), this.toPx(w), this.toPx(h));
- if (borderWidth > 0) {
- this.ctx.setStrokeStyle(borderColor);
- this.ctx.setLineWidth(this.toPx(borderWidth));
- this.ctx.stroke();
- }
- } else {
- this.ctx.drawImage(imgPath, this.toPx(sx), this.toPx(sy), this.toPx(sw), this.toPx(sh), this.toPx(x), this.toPx(y), this.toPx(w), this.toPx(h));
- }
- this.ctx.restore();
- },
- /**
- * 渲染线
- * @param {*} param0
- */
- drawLine({ startX, startY, endX, endY, color, width }) {
- this.ctx.save();
- this.ctx.beginPath();
- this.ctx.setStrokeStyle(color);
- this.ctx.setLineWidth(this.toPx(width));
- this.ctx.moveTo(this.toPx(startX), this.toPx(startY));
- this.ctx.lineTo(this.toPx(endX), this.toPx(endY));
- this.ctx.stroke();
- this.ctx.closePath();
- this.ctx.restore();
- },
- downloadResource(images = []) {
- const drawList = [];
- this.drawArr = [];
- images.forEach((image, index) => drawList.push(this._downloadImageAndInfo(image, index)));
- return Promise.all(drawList);
- },
- initCanvas(w, h, debug) {
- return new Promise((resolve) => {
- this.pxWidth = this.toPx(w)
- this.pxHeight = this.toPx(h)
- this.debug = debug
- setTimeout(() => {
- resolve()
- }, 0)
- });
- },
- /**
- * handle
- -----------------------*/
- /**
- * 画圆角矩形
- */
- _drawRadiusRect(x, y, w, h, r) {
- const br = r / 2;
- this.ctx.beginPath();
- this.ctx.moveTo(this.toPx(x + br), this.toPx(y)); // 移动到左上角的点
- this.ctx.lineTo(this.toPx(x + w - br), this.toPx(y));
- this.ctx.arc(this.toPx(x + w - br), this.toPx(y + br), this.toPx(br), 2 * Math.PI * (3 / 4), 2 * Math.PI * (4 / 4))
- this.ctx.lineTo(this.toPx(x + w), this.toPx(y + h - br));
- this.ctx.arc(this.toPx(x + w - br), this.toPx(y + h - br), this.toPx(br), 0, 2 * Math.PI * (1 / 4))
- this.ctx.lineTo(this.toPx(x + br), this.toPx(y + h));
- this.ctx.arc(this.toPx(x + br), this.toPx(y + h - br), this.toPx(br), 2 * Math.PI * (1 / 4), 2 * Math.PI * (2 / 4))
- this.ctx.lineTo(this.toPx(x), this.toPx(y + br));
- this.ctx.arc(this.toPx(x + br), this.toPx(y + br), this.toPx(br), 2 * Math.PI * (2 / 4), 2 * Math.PI * (3 / 4))
- },
- /**
- * 计算文本长度
- * @param {Array|Object}} text 数组 或者 对象
- */
- _getTextWidth(text) {
- let texts = [];
- if (Object.prototype.toString.call(text) === '[object Object]') {
- texts.push(text);
- } else {
- texts = text;
- }
- let width = 0;
- texts.forEach(({ fontSize, text, marginLeft = 0, marginRight = 0 }) => {
- this.ctx.setFontSize(this.toPx(fontSize));
- width += this.ctx.measureText(text).width + marginLeft + marginRight;
- })
- return this.toRpx(width);
- },
- /**
- * 渲染一段文字
- */
- _drawSingleText({ x, y, fontSize, color, baseLine, textAlign = 'left', text, opacity = 1, textDecoration = 'none',
- width, lineNum = 1, lineHeight = 0, fontWeight = 'normal', fontStyle = 'normal', fontFamily = "sans-serif" }) {
- this.ctx.save();
- this.ctx.beginPath();
- this.ctx.font = fontStyle + " " + fontWeight + " " + this.toPx(fontSize, true) + "px " + fontFamily
- this.ctx.setGlobalAlpha(opacity);
- // this.ctx.setFontSize(this.toPx(fontSize));
- this.ctx.setFillStyle(color);
- this.ctx.setTextBaseline(baseLine);
- this.ctx.setTextAlign(textAlign);
- let textWidth = this.toRpx(this.ctx.measureText(text).width);
- const textArr = [];
- if (textWidth > width) {
- // 文本宽度 大于 渲染宽度
- let fillText = '';
- let line = 1;
- for (let i = 0; i <= text.length - 1; i++) { // 将文字转为数组,一行文字一个元素
- fillText = fillText + text[i];
- if (this.toRpx(this.ctx.measureText(fillText).width) >= width) {
- if (line === lineNum) {
- if (i !== text.length - 1) {
- fillText = fillText.substring(0, fillText.length - 1) + '...';
- }
- }
- if (line <= lineNum) {
- textArr.push(fillText);
- }
- fillText = '';
- line++;
- } else {
- if (line <= lineNum) {
- if (i === text.length - 1) {
- textArr.push(fillText);
- }
- }
- }
- }
- textWidth = width;
- } else {
- textArr.push(text);
- }
- textArr.forEach((item, index) => {
- this.ctx.fillText(item, this.toPx(x), this.toPx(y + (lineHeight || fontSize) * index));
- })
- this.ctx.restore();
- // textDecoration
- if (textDecoration !== 'none') {
- let lineY = y;
- if (textDecoration === 'line-through') {
- // 目前只支持贯穿线
- lineY = y;
- }
- this.ctx.save();
- this.ctx.moveTo(this.toPx(x), this.toPx(lineY));
- this.ctx.lineTo(this.toPx(x) + this.toPx(textWidth), this.toPx(lineY));
- this.ctx.setStrokeStyle(color);
- this.ctx.stroke();
- this.ctx.restore();
- }
- return textWidth;
- },
- /**
- * helper
- -----------------------*/
- /**
- * 下载图片并获取图片信息
- */
- _downloadImageAndInfo(image, index) {
- return new Promise((resolve, reject) => {
- const { x, y, url, zIndex, type } = image;
- const imageUrl = url;
-
- // 下载图片
- this._downImage(imageUrl, index, type)
- // 获取图片信息
- .then(imgPath => this._getImageInfo(imgPath, index))
- .then(({ imgPath, imgInfo }) => {
- // 根据画布的宽高计算出图片绘制的大小,这里会保证图片绘制不变形
- let sx;
- let sy;
- const borderRadius = image.borderRadius || 0;
- const setWidth = image.width;
- const setHeight = image.height;
- const width = this.toRpx(imgInfo.width);
- const height = this.toRpx(imgInfo.height);
- if (width / height <= setWidth / setHeight) {
- sx = 0;
- sy = (height - ((width / setWidth) * setHeight)) / 2;
- } else {
- sy = 0;
- sx = (width - ((height / setHeight) * setWidth)) / 2;
- }
- this.drawArr.push({
- type: 'image',
- borderRadius,
- borderWidth: image.borderWidth,
- borderColor: image.borderColor,
- zIndex: typeof zIndex !== 'undefined' ? zIndex : index,
- imgPath,
- sx,
- sy,
- sw: (width - (sx * 2)),
- sh: (height - (sy * 2)),
- x,
- y,
- w: setWidth,
- h: setHeight,
- });
- resolve();
- })
- .catch(err => reject(err));
- });
- },
- /**
- * 下载图片资源
- * @param {*} imageUrl
- */
- _downImage(imageUrl,index,type) {
- return new Promise((resolve, reject) => {
- if (/^http/.test(imageUrl) && !new RegExp(wx.env.USER_DATA_PATH).test(imageUrl)&&type!=='qrcode') {
- wx.downloadFile({
- url: this._mapHttpToHttps(imageUrl),
- success: (res) => {
- if (res.statusCode === 200) {
- resolve(res.tempFilePath);
- } else {
- reject(res.errMsg);
- }
- },
- fail(err) {
- reject(err);
- },
- });
- } else {
- // 支持本地地址
- resolve(imageUrl);
- }
- });
- },
- /**
- * 获取图片信息
- * @param {*} imgPath
- * @param {*} index
- */
- _getImageInfo(imgPath, index) {
- return new Promise((resolve, reject) => {
- wx.getImageInfo({
- src: imgPath,
- success(res) {
- resolve({ imgPath, imgInfo: res, index });
- },
- fail(err) {
- reject(err);
- },
- });
- });
- },
- toPx(rpx, int) {
- // console.log('toPx',this.factor)
- if (int) {
- return parseInt(rpx * this.factor);
- }
- return rpx * this.factor;
- },
- toRpx(px, int) {
- // console.log('toRpx',this.factor)
- if (int) {
- return parseInt(px / this.factor);
- }
- return px / this.factor;
- },
- /**
- * 将http转为https
- * @param {String}} rawUrl 图片资源url
- */
- _mapHttpToHttps(rawUrl) {
- if (rawUrl.indexOf(':') < 0) {
- return rawUrl;
- }
- const urlComponent = rawUrl.split(':');
- if (urlComponent.length === 2) {
- if (urlComponent[0] === 'http') {
- urlComponent[0] = 'https';
- return `${urlComponent[0]}:${urlComponent[1]}`;
- }
- }
- return rawUrl;
- },
- },
- created() {
- const sysInfo = uni.getSystemInfoSync();
- const screenWidth = sysInfo.screenWidth;
- this.factor = screenWidth / 750;
- // console.log('created')
- },
- };
- </script>
- <style lang="scss">
- .dt-canvas {
- width: 750upx;
- height: 750upx;
- }
- .dt-canvas.pro {
- position: absolute;
- bottom: 0;
- left: 0;
- transform: translate3d(-9999upx, 0, 0);
- }
- .dt-canvas.debug {
- position: absolute;
- bottom: 0;
- left: 0;
- border: 1upx solid #ccc;
- }
- </style>
|