下载图片资源

    将图片资源放到 asset/img 下面。

    在项目里面我们可能用到 requestAnimationFrame ,关于兼容,大家请查看 目前 requestAnimationFrame 兼容性


    为了语义化,规范化,大家可以参考以下规范。

    写出一份优雅的代码,是每一位工程师的责任。


    • 首先修改我们的 index.html
    1. <!DOCTYPE html>
    2. <html lang="zh-CN">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Luvdisc</title>
    6. <link rel="stylesheet" href="assets/css/main.css" />
    7. </head>
    8. <body>
    9. <!--
    10. HTML 属性应当按照以下给出的顺序依次排列,确保代码的易读性。
    11. class
    12. id 、 name
    13. data-*
    14. src、for、 type、 href
    15. title、alt
    16. aria-*、 role
    17. -->
    18. <section class="wrapper">
    19. <figure class="canvas-container">
    20. <canvas class="canvas-item" id="one" width="800" height="600"></canvas>
    21. <canvas class="canvas-item" id="two" width="800" height="600"></canvas>
    22. </figure>
    23. </section>
    24. <script async src="bundle.js"></script>
    25. </body>
    26. </html>
    • 添加 main.css
    1. /*
    2. CSS书写顺序
    3. 1.位置属性(position, top, right, z-index, display, float等)
    4. 2.大小(width, height, padding, margin)
    5. 3.文字系列(font, line-height, letter-spacing, color- text-align等)
    6. 4.背景(background, border等)
    7. 5.其他(animation, transition等)
    8. */
    9. .wrapper{
    10. width: 800px;
    11. height: 600px;
    12. padding-top: 10px;
    13. margin: 0 auto;
    14. }
    15. .canvas-container{
    16. position: relative;
    17. width: 800px;
    18. height: 600px;
    19. margin: 0;
    20. }
    21. canvas.canvas-item{
    22. position: absolute;
    23. top: 0;
    24. right: 0;
    25. bottom: 0;
    26. left: 0;
    27. }
    28. canvas#one{
    29. z-index: 1;
    30. }
    31. canvas#two{
    32. z-index: 0;
    33. }

    首先我们封装获取一个获取 DOM 和 context 的函数

    1. function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
    2. const dom = <HTMLCanvasElement>document.querySelector('#' + id);
    3. const ctx = dom.getContext('2d');
    4. return [dom, ctx];
    5. }

    为什么会知道是 HTMLCanvasElement ?

    把鼠标移动到 document.querySelector 上面,按 F12,跳转到原生定义文件里面,搜索 canvas 就可以搜索得到,因为是 HTMLEelemnt 实例,所以必然有 Element 单词在里面。

    而这个 CanvasRenderingContext2D ,直接查看 dom.getContext 的返回值类型即可知道。

    这里的返回值我们用了元组的解构。

    所以获取我们应该这样解构,并且放在了 window.onload 函数里面,报错 DOM 加载完成之后再执行代码。

    1. window.onload = () => {
    2. const [cvs_one, ctx_one] = getCanvasAndContextById('one');
    3. const [cvs_two, ctx_two] = getCanvasAndContextById('two');
    4. }

    我们设置的 #one 的 z-index 层级比较高,所以我们会在 one 这一层上面绘制一些 UI、结构之类的逻辑,而 two 则是绘制背景。

    • 接下来我们要用到 requestAnimationFrame 来优化 Web 动画性能

    大家可以看一下这一篇文章 requestAnimationFrame简介

    我们知道游戏的运行,是需要浏览器不停的重新绘制的,所以说一定有一个循环,在不停的重绘。

    1. let lastTime: number = Date.now(), // 记录上一次绘制的时间
    2. deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界
    3. function gameLoop() {
    4. const now = Date.now()
    5. deltaTime = now - lastTime;
    6. lastTime = now;
    7. console.log(deltaTime);
    8. requestAnimationFrame(gameLoop);
    9. }

    这里有一个 deltaTime 来记录一下每一次requestAnimationFrame的时间

    而我们的 DOM 实例和 Context 是需要在其他函数里面使用的,所以说,我们必须拿到外面来,但是又必须等 DOM 完成之后再获取。

    所有我们小小的修改一下之前的代码

    1. function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
    2. const dom = <HTMLCanvasElement>document.querySelector('#' + id);
    3. const ctx = dom.getContext('2d');
    4. return [dom, ctx];
    5. }
    6. let cvs_one: HTMLCanvasElement,
    7. cvs_two: HTMLCanvasElement,
    8. ctx_one: CanvasRenderingContext2D,
    9. ctx_two: CanvasRenderingContext2D;
    10. let lastTime: number = Date.now(), // 记录上一次绘制的时间
    11. deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界
    12. window.onload = () => {
    13. init();
    14. gameLoop();
    15. }
    16. function init() {
    17. [cvs_one, ctx_one] = getCanvasAndContextById('one');
    18. [cvs_two, ctx_two] = getCanvasAndContextById('two');
    19. }
    20. function gameLoop() {
    21. const now = Date.now()
    22. deltaTime = now - lastTime;
    23. lastTime = now;
    24. console.log(deltaTime);
    25. requestAnimationFrame(gameLoop);
    26. }
    • 接下来我们添加绘制背景的函数

    因为背景是处于游戏中,所以我们要把它写到游戏循环里面。

    1. const bgPic = new Image();
    2. function init() {
    3. [cvs_one, ctx_one] = getCanvasAndContextById('one');
    4. [cvs_two, ctx_two] = getCanvasAndContextById('two');
    5. bgPic.src = 'assets/img/background.jpg';
    6. cvs_width = cvs_one.width;
    7. cvs_height = cvs_one.height;
    8. }
    9. function gameLoop() {
    10. const now = Date.now()
    11. deltaTime = now - lastTime;
    12. lastTime = now;
    13. console.log(deltaTime);
    14. drawBackbround()
    15. requestAnimationFrame(gameLoop);
    16. }
    17. function drawBackbround() {
    18. ctx_two.drawImage(bgPic, 0, 0, cvs_width, cvs_height)
    19. }

    现在你应该可以看到一个背景图片了。

    这样的代码看起来非常的乱,现在我们该来想一想如何重构了。

    首先我们理清逻辑,初始化 Init -> 游戏循环 ,目前来说就这么2个逻辑,所以,我们把这 2 个函数分成 2 个文件。

    分别创建 init.ts 、game-loop.ts 文件

    1. // init.ts
    2. let cvs_one: HTMLCanvasElement,
    3. cvs_two: HTMLCanvasElement,
    4. ctx_one: CanvasRenderingContext2D,
    5. ctx_two: CanvasRenderingContext2D;
    6. let cvs_width: number,
    7. cvs_height: number;
    8. const bgPic = new Image();
    9. function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
    10. const dom = <HTMLCanvasElement>document.querySelector('#' + id);
    11. const ctx = dom.getContext('2d');
    12. return [dom, ctx];
    13. }
    14. function init() {
    15. [cvs_one, ctx_one] = getCanvasAndContextById('one');
    16. [cvs_two, ctx_two] = getCanvasAndContextById('two');
    17. bgPic.src = 'assets/img/background.jpg';
    18. cvs_width = cvs_one.width;
    19. cvs_height = cvs_one.height;
    20. }
    21. export default init;
    22. export { bgPic, cvs_width , cvs_height, ctx_two };
    1. // game-loop.ts
    2. import { bgPic, cvs_width , cvs_height, ctx_two } from "./init";
    3. let lastTime: number = Date.now(), // 记录上一次绘制的时间
    4. deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界
    5. function gameLoop() {
    6. const now = Date.now()
    7. deltaTime = now - lastTime;
    8. lastTime = now;
    9. console.log(deltaTime);
    10. drawBackbround()
    11. requestAnimationFrame(gameLoop);
    12. }
    13. function drawBackbround() {
    14. ctx_two.drawImage(bgPic, 0, 0, cvs_width, cvs_height)
    15. }
    16. export default gameLoop;
    1. // main.ts
    2. import gameLoop from "./game-loop";
    3. import init from "./init";
    4. window.onload = () => {
    5. init();
    6. gameLoop();
    7. }

    重构搞定!