假如只剩下Canvas標(biāo)簽
一、背景
如果只剩下canvas標(biāo)簽,該如何去繪制頁面中的內(nèi)容呢?這也許是一個偽命題,但是用canvas確事能夠幫助完成很多事。今天就用canvas+AST語法樹構(gòu)建一個信息流樣式。
二、繪制流程
將整個繪制流程分為三部分:基本元素、AST語法樹、主函數(shù)類。基本元素指的是圖片、文字、矩形、圓等;AST語法樹在本處值得就是包含一些屬性的js對象;主函數(shù)類指對外暴露的接口,通過調(diào)用實現(xiàn)最終繪制。
2.1 基本元素
不管多么復(fù)雜的事物肯定都是由一系列簡單的元素組成,例如汽車肯定是通過一些簡單的機(jī)械零配件組成;電腦也是通過電阻、電容等零配件組成。網(wǎng)頁也不例外,也是通過文字、圖片、矩形等組成。
2.1.1 加載圖片
圖片是一個頁面中的靈魂元素,在頁面中占據(jù)絕大部分空間。
- class DrawImage {
- constructor(ctx, imageObj) {
- this.ctx = ctx;
- this.imageObj = imageObj;
- }
- draw() {
- const {centerX, centerY, src, sx = 1, sy = 1} = this.imageObj;
- const img = new Image();
- img.onload = () => {
- const imgWidth = img.width;
- const imgHeight = img.height;
- this.ctx.save();
- this.ctx.scale(sx, sy);
- this.ctx.drawImage(img, centerX - imgWidth * sx / 2, centerY - imgHeight * sy / 2);
- this.ctx.restore();
- };
- img.src = src;
- }
- }
2.1.2 繪制文字
文字能夠提高頁面的可讀性,讓觀察該頁面的每一個人都能夠快速了解該頁面的思想。
- class DrawText {
- constructor(ctx, textObj) {
- this.ctx = ctx;
- this.textObj = textObj;
- }
- draw() {
- const {x, y, font, content, lineHeight = 20, width, fillStyle = '#000000', textAlign = 'start', textBaseline = 'middle'} = this.textObj;
- const branchsContent = this.getBranchsContent(content, width);
- this.ctx.save();
- this.ctx.fillStyle = fillStyle;
- this.ctx.textAlign = textAlign;
- this.ctx.textBaseline = textBaseline;
- this.ctx.font = font;
- branchsContent.forEach((branchContent, index) => {
- this.ctx.fillText(branchContent, x, y + index * lineHeight);
- });
- this.ctx.restore();
- }
- getBranchsContent(content, width) {
- if (!width) {
- return [content];
- }
- const charArr = content.split('');
- const branchsContent = [];
- let tempContent = '';
- charArr.forEach(char => {
- if (this.ctx.measureText(tempContent).width < width && this.ctx.measureText(tempContent + char).width <= width) {
- tempContent += char;
- }
- else {
- branchsContent.push(tempContent);
- tempContent = '';
- }
- });
- branchsContent.push(tempContent);
- return branchsContent;
- }
- }
2.1.3 繪制矩形
通過矩形元素能夠與文字等元素配合達(dá)到意想不到的效果。
- class DrawRect {
- constructor(ctx, rectObj) {
- this.ctx = ctx;
- this.rectObj = rectObj;
- }
- draw() {
- const {x, y, width, height, fillStyle, lineWidth = 1} = this.rectObj;
- this.ctx.save();
- this.ctx.fillStyle = fillStyle;
- this.ctx.lineWidth = lineWidth;
- this.ctx.fillRect(x, y, width, height);
- this.ctx.restore();
- }
- }
2.1.4 繪制圓
圓與矩形承擔(dān)的角色一致,也是在頁面中比較重要的角色。
- class DrawCircle {
- constructor(ctx, circleObj) {
- this.ctx = ctx;
- this.circleObj = circleObj;
- }
- draw() {
- const {x, y, R, startAngle = 0, endAngle = Math.PI * 2, lineWidth = 1, fillStyle} = this.circleObj;
- this.ctx.save();
- this.ctx.lineWidth = lineWidth;
- this.ctx.fillStyle = fillStyle;
- this.ctx.beginPath();
- this.ctx.arc(x, y, R, startAngle, endAngle);
- this.ctx.closePath();
- this.ctx.fill();
- this.ctx.restore();
- }
- }
2.2 AST樹
AST抽象語法樹是源代碼語法結(jié)構(gòu)的一種抽象表示。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。例如,在Vue中,將模板語法轉(zhuǎn)換為AST抽象語法樹,然后再將抽象語法樹轉(zhuǎn)換為HTML結(jié)構(gòu),咱們在利用canvas繪制頁面時也利用AST抽象語法樹來表示頁面中的內(nèi)容,實現(xiàn)的類型有rect(矩形)、img(圖片)、text(文字)、circle(圓)。
本次將繪制的內(nèi)容包含靜態(tài)頁面部分和動畫部分,所以將利用兩個canvas實現(xiàn),每個canvas將對應(yīng)一個AST樹,分別為靜態(tài)部分AST樹和動態(tài)部分AST樹。
2.2.1 靜態(tài)部分AST樹
本次繪制的頁面中靜態(tài)部分的AST樹如下所示,包含矩形、圖片、文字。
- const graphicAst = [
- {
- type: 'rect',
- x: 0,
- y: 0,
- width: 1400,
- height: 400,
- fillStyle: '#cec9ae'
- },
- {
- type: 'img',
- centerX: 290,
- centerY: 200,
- sx: 0.9,
- sy: 0.9,
- src: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_match%2F0%2F11858683821%2F0.jpg&refer=http%3A%2F%2Finews.gtimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1622015341&t=cc1bd95777dfa37d88c48bb6e179778e'
- },
- {
- type: 'text',
- x: 600,
- y: 60,
- textAlign: 'start',
- textBaseline: 'middle',
- font: 'normal 40px serif',
- lineHeight: 50,
- width: 180,
- fillStyle: '#000000',
- content: '灰太狼是最好的一頭狼,它每天都在夢想著吃羊,一直沒有實現(xiàn),但是從不氣餒。'
- },
- {
- type: 'text',
- x: 600,
- y: 170,
- textAlign: 'start',
- textBaseline: 'middle',
- font: 'normal 30px serif',
- lineHeight: 50,
- width: 180,
- fillStyle: '#7F7F7F',
- content: '為灰太狼加油、為灰太狼喝彩,😄'
- },
- {
- type: 'text',
- x: 1200,
- y: 360,
- textAlign: 'start',
- textBaseline: 'ideographic',
- font: 'normal 30px serif',
- lineHeight: 50,
- width: 180,
- fillStyle: '#949494',
- content: '閱讀'
- },
- {
- type: 'text',
- x: 1260,
- y: 363,
- textAlign: 'start',
- textBaseline: 'ideographic',
- font: 'normal 30px serif',
- lineHeight: 50,
- width: 180,
- fillStyle: '#949494',
- content: '520'
- }
- ];
2.2.2 動態(tài)部分AST樹
本次繪制的頁面中動畫部分的AST樹動態(tài)生成,由一系列動態(tài)顏色的圓組成。
- function getMarqueeAst(startX, endX, count, options = {}) {
- const {y = 15, R = 15} = options;
- if (!(endX >= startX && count > 0)) {
- return [];
- }
- const interval = (endX - startX) / count;
- const marqueeAstArr = [];
- for (let i = 0; i < count; i++) {
- const RValue = Math.random() * 255;
- const GValue = Math.random() * 255;
- const BValue = Math.random() * 255;
- const fillStyle = `rgb(${RValue}, ${GValue}, ${BValue})`;
- marqueeAstArr.push({
- type: 'circle',
- x: startX + i * interval,
- y,
- R,
- fillStyle
- });
- }
- return marqueeAstArr;
- }
2.3 主函數(shù)類
除了上述一些基本元素類,將通過一個主函數(shù)類對外進(jìn)行暴露。
- class Draw {
- constructor(canvasDom) {
- this._canvasDom = canvasDom;
- this.ctx = this._canvasDom.getContext('2d');
- this.width = this._canvasDom.width;
- this.height = this._canvasDom.height;
- }
- // 繪制函數(shù)
- draw(ast) {
- ast.forEach(elementObj => {
- this.drawFactory(elementObj);
- const {children} = elementObj;
- // 遞歸調(diào)用
- if (children && Array.isArray(children)) {
- this.draw(children);
- }
- });
- }
- // 工廠模型繪制對應(yīng)基本元素
- drawFactory(elementObj) {
- const {type} = elementObj;
- switch(type) {
- case 'img': {
- this.drawImage(elementObj);
- break;
- }
- case 'text': {
- this.drawText(elementObj);
- break;
- }
- case 'rect': {
- this.drawRect(elementObj);
- break;
- }
- case 'circle': {
- this.drawCircle(elementObj);
- break;
- }
- }
- }
- drawImage(imageObj) {
- const drawImage = new DrawImage(this.ctx, imageObj);
- drawImage.draw();
- }
- drawText(textObj) {
- const drawText = new DrawText(this.ctx, textObj);
- drawText.draw();
- }
- drawRect(rectObj) {
- const drawRect = new DrawRect(this.ctx, rectObj);
- drawRect.draw();
- }
- drawCircle(circleObj) {
- const drawCircle = new DrawCircle(this.ctx, circleObj);
- drawCircle.draw();
- }
- clearCanvas() {
- this.ctx.clearRect(0, 0, this.width, this.height);
- }
- }
2.4 內(nèi)容繪制
前面的準(zhǔn)備工作已經(jīng)完成,下面將各個函數(shù)和AST樹聯(lián)動起來,達(dá)到想要的效果。
2.4.1 靜態(tài)內(nèi)容繪制
先將靜態(tài)部分的內(nèi)容繪制好,作為頁面的基石。
- const basicCanvasDom = document.getElementById('basicCanvas');
- const drawBasicInstance = new Draw(basicCanvasDom);
- drawBasicInstance.draw(graphicAst);
靜態(tài)內(nèi)容.png
2.4.2 繪制動畫跑馬燈
再給該部分內(nèi)容來點(diǎn)動畫效果,更加激動人心。
- const animationCanvasDom = document.getElementById('animationCanvas');
- const drawAnimationInstance = new Draw(animationCanvasDom);
- let renderCount = 0;
- function animate() {
- if (renderCount % 5 === 0) {
- drawAnimationInstance.clearCanvas();
- drawAnimationInstance.draw(getMarqueeAst(20, 1440, 22));
- drawAnimationInstance.draw(getMarqueeAst(20, 1440, 22, {
- y: 380
- }));
- }
- window.requestAnimationFrame(animate);
- renderCount++;
- }
- animate();
本文轉(zhuǎn)載自微信公眾號「執(zhí)鳶者」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系執(zhí)鳶者公眾號。