前面說的話
某產(chǎn)品:這個版本我想要做個我們的內(nèi)容可以生成海報的功能,
用戶可以保存海報,然后在朋友圈分享,
巴拉巴拉
。。。
。。
。
,現(xiàn)在UI還在設(shè)計一款比較漂亮的海報。。。
某前端(心里想):希望布局不要太復(fù)雜,不然用canvas生成的話,又得加班加點了。
(眉頭一皺):嗯,可以,到時候UI出來設(shè)計稿的時候,我就開始動工。
然后UI出圖了。【我們UI一般把圖上傳至 藍(lán)湖 這樣比較好看一些尺寸信息,下面的圖片也是從 藍(lán)湖 上截圖的】
感覺還不錯,不會很復(fù)雜的樣子,(以這張海報先舉個例子)
看到這幅圖的時候,你要做的幾件事:
1.首先問問UI,這是不是最終版本
2.確定好是最終版本后,我們開始在這個海報頁上區(qū)分,哪些是變量,哪些是固定元素/切圖,哪些是UI需要提供的切圖
3.確定好之后,我們就可以開始動工了
接下來我們就來看看幾個需要實現(xiàn)的技術(shù)點
1.海報中間的圖片需要怎么樣去控制,才能顯示得“好(保持縱橫比縮放圖片,只保證圖片的短邊能完全顯示出來也就是說,圖片通常只在水平或垂直方向是完整的,另一個方向?qū)l(fā)生截取。)”?
2.底部的文字如何實現(xiàn)換行?
3.分享自@xxx 的文字如果太長怎么辦?
我們一一來解決這些問題吧
基礎(chǔ)準(zhǔn)備,設(shè)計稿一般都為750*1334,一般來說我們會讓設(shè)計稿標(biāo)注各個元素的間距,這個間距是px單位的,
但是小程序是rpx單位的,怎么建立起對應(yīng)關(guān)系,而又在開發(fā)中提升效率呢
舉個例子
首先我們創(chuàng)建一個叫做poster的小程序頁面
我們在wxml,這里有三個變量,分別是cardCreateImgUrl【canvas生成的圖片路徑】、WIDTH【設(shè)計稿的寬度】、HEIGHT【設(shè)計稿的高度】
<!-- ... -->
<view id="posterWrp">
<view class="cardCreateWrp">
<image src="{{cardCreateImgUrl}}" mode="aspectFill" class="imgCardWrp" bindload="bindload"
style="width:{{WIDTH}}rpx;height:{{HEIGHT}}rpx;"></image>
</view>
<canvas class="myCanvas" canvas-id="myCanvas" style="width:{{WIDTH}}px;height:{{HEIGHT}}px;" ></canvas>
</view>
<!-- ... -->
在wxss中,我們把canvas的top設(shè)置成-9999px ,
其實canvas是存在頁面的,只是我們把它用這種方式隱藏了,
這樣做的目的,就相當(dāng)于canvas我們現(xiàn)在只用來作為一個畫圖工具,
我們畫好canvas并把它生成的圖片路徑賦值到image標(biāo)簽的src來進行顯示,
這就是我們提到的提高開發(fā)效率的第一步,
我們現(xiàn)在看到的是一張已經(jīng)生成好的圖片,而不是canvas
// ...
.cardCreateWrp image {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.myCanvas {
position: absolute;
top: -9999px;
left: 50%;
transform: translate(-50%, -50%);
}
// ...
在js中,
我們根據(jù)設(shè)計稿的寬高來設(shè)置
data: {
// ...
WIDTH: 750,
HEIGHT: 1148,
cardCreateImgUrl: ''
// ...
},
我們在一開始畫canvas的時候就要把canvas生成圖片了
// ...
// 前面是一些canvans的操作,略過
// ...
ctx.draw(false, () => {
// 生成圖片
wxp.canvasToTempFilePath({
canvasId: 'myCanvas'
}).then(({
tempFilePath
}) => {
this.setData({
cardCreateImgUrl: tempFilePath
});
});
});
這樣子的話,我們一開始就是一張圖片,邊畫畫布的時候,就能看到圖片生成的樣子。
這些都是準(zhǔn)備工作,準(zhǔn)備好了之后我們就可以開始解決我們在畫圖過程中的技術(shù)點了。
1.海報中間的圖片需要怎么樣去控制,才能顯示得“好(保持縱橫比縮放圖片,只保證圖片的短邊能完全顯示出來。也就是說,圖片通常只在水平或垂直方向是完整的,另一個方向?qū)l(fā)生截取。)”?
這里就要涉及到畫布的clip()方法和我們用js去計算了,
不清楚clip的話可以先去微信小程序官網(wǎng)api了解一下,
了解以后我們來看以下的代碼
代碼就不多解釋了,反正就是這些計算就是讓圖片能更好的顯示
// ...
wxp.getImageInfo({
// 七牛云做壓縮
src: `${data.imageUrl}?imageView2/1/w/621/h/668/q/50`
}).then((res) => {
// 計算海報寬高
const scale1 = res.width / res.height;
const scale2 = 621 / 668;
let drawW = 0,
drawH = 0,
mt = 0,
ml = 0;
if (scale1 > scale2) {
drawH = 668;
drawW = 668 * scale1;
ml = (621 - drawW) / 2;
} else {
drawW = 621;
drawH = drawW / scale1;
mt = (668 - drawH) / 2;
}
ctx.save();
ctx.beginPath();
ctx.strokeStyle = "rgba(0,0,0,0)";
ctx.rect(66, 251, 621, 668);
ctx.closePath();
ctx.stroke();
ctx.clip();
// 畫圖片
ctx.drawImage(res.path, ml + 66, mt + 251, drawW, drawH);
ctx.restore();
// ...
});
// ...
這樣子,我們第一個技術(shù)點就解決了
2.底部的文字如何實現(xiàn)換行
我們都知道canvas不像我們在寫html頁面時,如果文字太長了,會自動換行。
所以我們應(yīng)該怎么做呢
這里我們用到了 measureText() 這個方法
我們把文字過長換行的方法封裝了一下
我們創(chuàng)建了一個名為canvas-text-break.js 的js文件
/**
* @desc canvas文字過長換行腳本
* @param {Object} ctx canvas對象
* @param {String} text 文字
* @param {Number} x 距離左邊的寬度
* @param {Number} y 距離右邊的寬度
* @param {Number} w 文本區(qū)域的寬度
* @param {Object} fontStyle 文本的字體風(fēng)格/位置,有默認(rèn)值
*/
const CTB = ({
ctx,
text,
x,
y,
w,
fontStyle: {
lineHeight = 60,
textAlign = 'left',
textBaseline = 'top',
font = 'normal 40px arial',
fillStyle = '#000000'
}
}) => {
ctx.save();
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
const chr = text.split('');
const row = [];
let temp = '';
/*
判斷如果末尾是,!。》 就不要換行
判斷如果末尾是《 就要換行
*/
for (let a = 0; a < chr.length; a++) {
if (ctx.measureText(temp).width < w) { } else {
if (/[,。!》]/im.test(chr[a])) {
// console.log(`我是${chr[a]},我在末尾,我不換行`);
temp += ` ${chr[a]}`;
// 跳過這個字符
a++;
}
if (/[《]/im.test(chr[a - 1])) {
// console.log(`我是${chr[a-1]},我在末尾,我要換行`);
// 刪除這個字符
temp = temp.substr(0, temp.length - 1);
a--;
}
row.push(temp);
temp = '';
}
temp += chr[a] ? chr[a] : '';
}
row.push(temp);
for (let b = 0; b < row.length; b++) {
ctx.fillText(row[b], x, y + b * lineHeight);
}
ctx.restore();
};
export default CTB;
根據(jù)上面這個圖,我們就可以獲取信息
x: 64,
y: 988,
w: 350,
fontStyle: {
lineHeight: 39,
textAlign: 'left',
textBaseline: 'top',
font: 'normal 23px arial',
fontSize: 23,
fillStyle: '#000000'
}
然后在js中我們這樣子做
import CTB from '../../utils/canvas-text-break';
let ctx = null;
// ...
// ...
ctx = wx.createCanvasContext('myCanvas');
const text = '你來人間一趟,你要看看太陽。和你的心上人一起走在街上。';
// ...
CTB({
ctx,
text,
x: 64,
y: 988,
w: 350,?
fontStyle: {
lineHeight: 39,
textAlign: 'left',
textBaseline: 'top',
font: 'normal 23px arial',
fontSize: 23,
fillStyle: '#000000'
}
});
這樣子,我們的第二個技術(shù)點也解決了
3.分享自@xxx 的文字如果太長怎么辦?
這個問題比較簡單
我們在js中這樣寫,記得
ctx.textBaseline = 'top';
ctx.textAlign = 'right';
這樣子的話文字多的話,就會往左邊延伸了,這個原理和我們在操作html文字的原理是類似的
ctx.save();
ctx.font = 'bold 25px arial';
ctx.fillStyle = '#000000';
ctx.textBaseline = 'top';
ctx.textAlign = 'right';
const {
nickName
} = wx.getStorageSync('user');
ctx.fillText(`分享自 @${nickName}`, WIDTH - 63, 1065);
ctx.restore();
這樣子,我們所有的技術(shù)點都解決了
注:wxp為我自己封裝的對象,并不是wx的對象,在這里 小程序篇-wx自帶api接入Promise,提升編程體驗
更新:可能有些人比較習(xí)慣看demo,這里分享一個 代碼片段
以上只是分享一些小技巧而已,我們在實際操作中肯定遇到的場景會更復(fù)雜,這些都是一些基礎(chǔ)要知道的。
——尼古拉斯·峰