最近遇到一個業務需求,在小程序端定製預覽功能,並在預覽的圖片中使用指定的外部字體。將預覽的圖片上傳OSS,後端生成PDF,在管理系統中下載。 可是…………,通過實踐發現,小程序儘管作了分包處理,依舊不能在本地存放字體包,把字體放OSS上返回,可是出現跨域,儘管配置了容許跨域,依舊不行。並且!!!小程序的canvas API
無法設置字體,沒有h5中canvas中的context.font = '字體名稱'
方法。最終決定曲線救國,放棄小程序端的預覽生成canvas功能,將canvas引入字體,生成圖片等操做放在管理系統中,採用原生canvas
來實現。前端
###在通常HTML
容器中,若是要實現文字的排版很容易。好比: 實現文本超出自動換行,默認文本超出容器寬度就會自動換行,也可使用word-wrap:break-word
實現強制換行。 實現文字豎排,有幾種方式:git
writing-mode
樣式:(存在兼容性問題)writing-mode:vertical-rl;//垂直方向自右而左的書寫方式。即 top-bottom-right-left
或者
writing-mode:vertical-lr;//垂直方向內內容從上到下,水平方向從左到右
複製代碼
具體效果如圖:github
可是這個對於瀏覽器也存在必定兼容性問題,使用的時候須要注意。ajax
寬度
控制換行:(不存在兼容性,推薦方式) 設置每行的寬度爲一個字大小,利用文本超出默認換行的特性,或者設置超出強制換行,實現文本豎排。br
標籤實現或者每一個文字存放一個標籤實現換行:(很死板的寫法,比較low,不推薦) 給每一個文字後添加br
標籤,或者每一個文字放一個標籤,這樣寫靈活性不高,很是不推薦!canvas
中實現文字排版canvas
中,若是文本超出canvas
大小,並不會自動換行,會直接在超出的後面繼續繪製成一排。 canvas
中也沒有直接能夠設置換行的api,那該怎麼實現換行呢? 能夠經過js
控制,經過計算當前繪製文字的x
座標,若是x座標大於canvas
的寬度,將x
座標賦值爲0
(繪製的起始點x座標),y
座標累加一個文字的高度,從而實現文本換行。y
座標累加,趟超過canvas
的高度時,將y
座標賦值爲0
(繪製的起始點y座標),x
座標累加一個文字的高度,從而實現豎排且文本換行。部分代碼片斷canvas
/**
* canvas繪製文字
* @param {CanvasRenderingContext2D對象} context
* @param {繪製內容} text
* @param {起始點x座標制} x
* @param {起始點y座標制} y
*/
drawTextVertical(context, text, x, y) {
let startX = x,
startY = y; //記錄開始的位置,用於文字換行賦值
let spaceCount = 0;
let arrText = text.trim().split('');
let formatText = text.replace(/\//g, '').split(''); // 去掉單斜槓
let align = context.textAlign;
let baseline = context.textBaseline;
context.textAlign = 'center';
context.textBaseline = 'middle';
context.font = 'Pacifico'
// 開始逐字繪製
arrText.forEach(function (letter, index) {
// 肯定下一個字符的縱座標位置
// 是否須要旋轉判斷
let code = letter.charCodeAt(0);
// 計算文字間距
let letterWidth = 22 * 2.3;
if (code <= 256) {
context.translate(x, y);
// 英文字符,旋轉90°
context.rotate(90 * Math.PI / 180);
context.translate(-x, -y);
}
if (code !== 47) context.fillText(letter, x, y);
// 旋轉座標系還原成初始態
context.setTransform(1, 0, 0, 1, 0, 0);
// 單斜槓換行或者長度超過8 此處要過濾在第9字是換行的符號的狀況
if ((code === 47 && !spaceCount) || (!spaceCount && index && index % 7 === 0)) {
// 單斜槓/ 表明換行 charCode=47
spaceCount += 1;
y = startY;
x = index ? (startX + letterWidth) : x;
startX = x;
} else if (code !== 47) {
// 若是是空格 減小字間距
if (code !== 32) {
y = y + letterWidth;
} else {
y = y + letterWidth / 2
}
}
});
// 水平垂直對齊方式還原
context.textAlign = align;
context.textBaseline = baseline;
}
複製代碼
canvas
生成圖片的時候能夠指定圖片格式(jpg,jpeg,png等),可是隻能生成位圖
(放大會失真)。若是想提升canvas
生成圖片的質量,能夠引入 hidpi-canvas-polyfill 插件,具體使用能夠參考這篇文章 解決canvas生成圖片模糊 。 canvas
生成圖片的背景默認是透明的,若是想單獨設置背景顏色,可使用ctx.fillStyle
進行填充,可是設置文字顏色,則文字顏色會覆蓋背景顏色,由於設置文字顏色也是使用ctx.fillStyle
。那麼,這種狀況可使用如下辦法解決: 1.使用canvas.getImageData
複製畫布上的像素數據 2.循環遍歷複製的每一個像素點,而後給每一個像素設置rgb
值 3.將設置好的流數據經過putImageData
放回畫布上。小程序
let imageData = ctx.getImageData(0, 0, width, height);
for (let i = 0; i < imageData.data.length; i += 4) {
// 當該像素是透明的,則設置成白色
if (imageData.data[i + 3] == 0) {
imageData.data[i] = 255;
imageData.data[i + 1] = 255;
imageData.data[i + 2] = 255;
imageData.data[i + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
複製代碼
可是要注意背景和文字的繪製順序,必須先繪製背景,再繪製文字
,若是順序顛倒,則文字會出現很明顯的鋸齒狀,有點模糊,這就和定位中z-index
原理相似。後端
1.首先引入字體庫,爲了節省本地空間,能夠從服務端引入,可是須要注意跨域問題, 也能夠將字體庫放本地,直接相對路徑引入。api
// 從服務端引入
@font-face {
font-family: "FZCUJINLJW";
src: url('https://www.xxxx.com/FZCUJINLJW.TTF') ;
}
// 本地引入
@font-face {
font-family: "FZCUJINLJW";
src: url('../../assets/FZCUJINLJW.TTF') ;
}
複製代碼
2.經過CanvasRenderingContext2D
對象設置字體,字號等跨域
ctx.font = '24px FZCUJINLJW';
ctx.fillStyle = '#db9a00';//填充顏色
複製代碼
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');//拿到一個CanvasRenderingContext2D對象
ctx.beginPath();// 開始繪製文字
ctx.font = `${FONT_SIZE}px FZCUJINLJW`;
ctx.fillStyle = '#db9a00';//填充顏色
ctx.fillText('繪製的內容', /*繪製的x座標*/, /*繪製的y座標*/);
let imgBase64 = canvas.toDataURL('image/png', 1);
ctx.closePath();
ctx.save();// 保存當前畫布內容
//若是須要在畫布上循環繪製屢次,須要手動清除畫布上已經保存的內容,若是不清除,則畫布內容會疊加。
ctx.clearRect(0, 0, canvasObj.width, canvasObj.height);
複製代碼
經過ctx.toDataURL
能夠獲取到畫布內容的base64編碼
瀏覽器
let imgBase64 = canvas.toDataURL('image/png', 1);
複製代碼
若是服務端支持使用base64
上傳,則不用處理,此處由於後端須要file文件類型,因此須要將base64
轉成file對象
,代碼以下:
let file = dataURLtoFile(imgBase64, 'jpg'); // 將base轉爲file對象
function dataURLtoFile(urlData, fileName) {
var bytes = window.atob(urlData.split(',')[1]); //去掉url的頭,並轉換爲byte
var mime = urlData.split(',')[0].match(/:(.*?);/)[1];
//處理異常,將ascii碼小於0的轉換爲大於0
var ab = new ArrayBuffer(bytes.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new File([ab], fileName, { type: mime });
}
複製代碼
轉成file對象
後,經過FormData
格式上傳
let formdata = new FormData();
formdata.append('multipartList', file);
ajax.post(url,data:formdata).then()
複製代碼
此處須要注意,當canvas
生成的圖片比較小時(好比5kb如下),有可能致使文件上傳失敗,我以前踩過此坑。
此處是和後端商量,將canvas
生成的圖片上傳服務端,並返回圖片的OSS
地址,再將此地址做爲參數傳給後端,獲取到PDF
的下載連接,前端經過window.open(url)
的方式實現文件下載。
let uploadUrl = window.interfercesPrefix + '/admin/goods/tbgoods/uploadImages';
let downLoadUrl = '/app/goods/tbgoods/downLoadPdf';
// 上傳圖片
ajaxUploderImg({ url: uploadUrl, data: formdata }).then(res => {
// 將圖片做爲參數獲取PDF下載地址
this.props.dispatch(downLoadPdf({ url: downLoadUrl, imgUrl: res.data }));
}).catch(err => {
if (err) {
notification['error']({
message: err.message,
description:
'圖片繪製出錯,請重試!',
});
} else {
notification['error']({
message: '下載出錯,請返回'
});
}
})
複製代碼