乐趣区

关于canvas:假如只剩下canvas标签

公众号“执鸢者”,回复“canvas”获取对应源码,还有业余交换群等你一起来洒脱。

一、背景

如果只剩下 canvas 标签,该如何去绘制页面中的内容呢?这兴许是一个伪命题,然而用 canvas 确事可能帮忙实现很多事。明天就用 canvas+AST 语法树构建一个信息流款式。

二、绘制流程

将整个绘制流程分为三局部:根本元素、AST 语法树、主函数类。根本元素指的是图片、文字、矩形、圆等;AST 语法树在本处值得就是蕴含一些属性的 js 对象;主函数类指对外裸露的接口,通过调用实现最终绘制。

2.1 根本元素

不论如许简单的事物必定都是由一系列简略的元素组成,例如汽车必定是通过一些简略的机械零配件组成;电脑也是通过电阻、电容等零配件组成。网页也不例外,也是通过文字、图片、矩形等组成。

2.1.1 加载图片

图片是一个页面中的灵魂元素,在页面中占据绝大部分空间。

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 绘制矩形

通过矩形元素可能与文字等元素配合达到意想不到的成果。

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 绘制圆

圆与矩形承当的角色统一,也是在页面中比拟重要的角色。

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 形象语法树是源代码语法结构的一种形象示意。它以树状的模式体现编程语言的语法结构,树上的每个节点都示意源代码中的一种构造。例如,在 Vue 中,将模板语法转换为 AST 形象语法树,而后再将形象语法树转换为 HTML 构造,咱们在利用 canvas 绘制页面时也利用 AST 形象语法树来示意页面中的内容,实现的类型有 rect(矩形)、img(图片)、text(文字)、circle(圆)。

本次将绘制的内容蕴含动态页面局部和动画局部,所以将利用两个 canvas 实现,每个 canvas 将对应一个 AST 树,别离为动态局部 AST 树和动静局部 AST 树。

2.2.1 动态局部 AST 树

本次绘制的页面中动态局部的 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: '灰太狼是最好的一头狼,它每天都在幻想着吃羊,始终没有实现,然而从不泄气。'
    },
    {
        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 动静局部 AST 树

本次绘制的页面中动画局部的 AST 树动静生成,由一系列动静色彩的圆组成。

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 主函数类

除了上述一些根本元素类,将通过一个主函数类对外进行裸露。

class Draw {constructor(canvasDom) {
        this._canvasDom = canvasDom;
        this.ctx = this._canvasDom.getContext('2d');
        this.width = this._canvasDom.width;
        this.height = this._canvasDom.height;
    }

    // 绘制函数
    draw(ast) {
        ast.forEach(elementObj => {this.drawFactory(elementObj);
            const {children} = elementObj;
            // 递归调用
            if (children && Array.isArray(children)) {this.draw(children);
            }
        });
    }

    // 工厂模型绘制对应根本元素
    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 内容绘制

后面的筹备工作曾经实现,上面将各个函数和 AST 树联动起来,达到想要的成果。

2.4.1 动态内容绘制

先将动态局部的内容绘制好,作为页面的基石。

const basicCanvasDom = document.getElementById('basicCanvas');
const drawBasicInstance = new Draw(basicCanvasDom);
drawBasicInstance.draw(graphicAst);

2.4.2 绘制动画跑马灯

再给该局部内容来点动画成果,更加激动人心。

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();

  1. 本文对应源码,关注公众号“执鸢者”,回复“canvas”获取
  2. 如果感觉这篇文章还不错,来个分享、点赞吧,让更多的人也看到
退出移动版