假如只剩下canvas标签

大家好,我是“执鸢者”,一位在前端圈摸爬滚打多年头发依然茂盛的“研发工程师”,致力于分享优秀文章,微信号“wjl20150403”。如果各位老铁感觉文章写得不错,欢迎点赞、关注本号主。回复“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);
静态内容.png
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.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号执鸢者,领取学习资料,定期为你推送原创深度好文

3.扫描下方添加进群,里面大佬多多,一起向他们学习

1. 图解JavaScript——代码实现(Object.create()、flat()等十四种代码原理实现不香吗?)

2. 图解JavaScript——代码实现【2】(重点是Promise、Async、发布/订阅原理实现)

3. 图解javascript——基础篇

4. 图解JavaScript——进阶篇

5. 十五张图带你彻底搞懂从URL到页面展示发生的故事

6. 图解浏览器安全(同源策略、XSS、CSRF、跨域、HTTPS、安全沙箱等串成糖葫芦)

7. 六张图带你从HTTP/0.9进化到HTTP3.0

8. (2.6w字)网络知识点灵魂拷问(上)——前端面试必问

9. (2.6w字)网络知识点灵魂拷问(下)——前端面试必问

10. 理论与API相结合理解Node中的网络通信

11. 硬核知识点——浏览器中的三类五种请求

12. 理论与实践相结合彻底理解CORS

13. 三步法解析Express源码

14. 一篇搞定前端高频手撕算法题(36道)

15. 十七张图玩转Node进程——榨干它

16. 理论与API相结合理解Node中的网络通信

17. 一文彻底搞懂前端监控

18. 前端的葵花宝典——架构

19. canvas从入门到猪头

20. 前端工程师的一大神器——puppeteer

21. 2021 年前端宝典【超三百篇】

22. 前端也要懂机器学习(上)

23. 前端也要懂机器学习(下)

24. 学架构助力前端起飞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值