将图片资源放到 asset/img 下面。
在项目里面我们可能用到 requestAnimationFrame ,关于兼容,大家请查看 目前 requestAnimationFrame 兼容性
为了语义化,规范化,大家可以参考以下规范。
写出一份优雅的代码,是每一位工程师的责任。
- 首先修改我们的 index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Luvdisc</title>
<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body>
<!--
HTML 属性应当按照以下给出的顺序依次排列,确保代码的易读性。
class
id 、 name
data-*
src、for、 type、 href
title、alt
aria-*、 role
-->
<section class="wrapper">
<figure class="canvas-container">
<canvas class="canvas-item" id="one" width="800" height="600"></canvas>
<canvas class="canvas-item" id="two" width="800" height="600"></canvas>
</figure>
</section>
<script async src="bundle.js"></script>
</body>
</html>
- 添加 main.css
/*
CSS书写顺序
1.位置属性(position, top, right, z-index, display, float等)
2.大小(width, height, padding, margin)
3.文字系列(font, line-height, letter-spacing, color- text-align等)
4.背景(background, border等)
5.其他(animation, transition等)
*/
.wrapper{
width: 800px;
height: 600px;
padding-top: 10px;
margin: 0 auto;
}
.canvas-container{
position: relative;
width: 800px;
height: 600px;
margin: 0;
}
canvas.canvas-item{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
canvas#one{
z-index: 1;
}
canvas#two{
z-index: 0;
}
首先我们封装获取一个获取 DOM 和 context 的函数
function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
const dom = <HTMLCanvasElement>document.querySelector('#' + id);
const ctx = dom.getContext('2d');
return [dom, ctx];
}
为什么会知道是 HTMLCanvasElement ?
把鼠标移动到 document.querySelector 上面,按 F12,跳转到原生定义文件里面,搜索 canvas
就可以搜索得到,因为是 HTMLEelemnt 实例,所以必然有 Element 单词在里面。
而这个 CanvasRenderingContext2D ,直接查看 dom.getContext 的返回值类型即可知道。
这里的返回值我们用了元组的解构。
所以获取我们应该这样解构,并且放在了 window.onload 函数里面,报错 DOM 加载完成之后再执行代码。
window.onload = () => {
const [cvs_one, ctx_one] = getCanvasAndContextById('one');
const [cvs_two, ctx_two] = getCanvasAndContextById('two');
}
我们设置的 #one 的 z-index
层级比较高,所以我们会在 one 这一层上面绘制一些 UI、结构之类的逻辑,而 two 则是绘制背景。
- 接下来我们要用到 requestAnimationFrame 来优化 Web 动画性能
大家可以看一下这一篇文章 requestAnimationFrame简介 。
我们知道游戏的运行,是需要浏览器不停的重新绘制的,所以说一定有一个循环,在不停的重绘。
let lastTime: number = Date.now(), // 记录上一次绘制的时间
deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界
function gameLoop() {
const now = Date.now()
deltaTime = now - lastTime;
lastTime = now;
console.log(deltaTime);
requestAnimationFrame(gameLoop);
}
这里有一个 deltaTime 来记录一下每一次requestAnimationFrame的时间
而我们的 DOM 实例和 Context 是需要在其他函数里面使用的,所以说,我们必须拿到外面来,但是又必须等 DOM 完成之后再获取。
所有我们小小的修改一下之前的代码
function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
const dom = <HTMLCanvasElement>document.querySelector('#' + id);
const ctx = dom.getContext('2d');
return [dom, ctx];
}
let cvs_one: HTMLCanvasElement,
cvs_two: HTMLCanvasElement,
ctx_one: CanvasRenderingContext2D,
ctx_two: CanvasRenderingContext2D;
let lastTime: number = Date.now(), // 记录上一次绘制的时间
deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界
window.onload = () => {
init();
gameLoop();
}
function init() {
[cvs_one, ctx_one] = getCanvasAndContextById('one');
[cvs_two, ctx_two] = getCanvasAndContextById('two');
}
function gameLoop() {
const now = Date.now()
deltaTime = now - lastTime;
lastTime = now;
console.log(deltaTime);
requestAnimationFrame(gameLoop);
}
- 接下来我们添加绘制背景的函数
因为背景是处于游戏中,所以我们要把它写到游戏循环里面。
const bgPic = new Image();
function init() {
[cvs_one, ctx_one] = getCanvasAndContextById('one');
[cvs_two, ctx_two] = getCanvasAndContextById('two');
bgPic.src = 'assets/img/background.jpg';
cvs_width = cvs_one.width;
cvs_height = cvs_one.height;
}
function gameLoop() {
const now = Date.now()
deltaTime = now - lastTime;
lastTime = now;
console.log(deltaTime);
drawBackbround()
requestAnimationFrame(gameLoop);
}
function drawBackbround() {
ctx_two.drawImage(bgPic, 0, 0, cvs_width, cvs_height)
}
现在你应该可以看到一个背景图片了。
这样的代码看起来非常的乱,现在我们该来想一想如何重构了。
首先我们理清逻辑,初始化 Init -> 游戏循环 ,目前来说就这么2个逻辑,所以,我们把这 2 个函数分成 2 个文件。
分别创建 init.ts 、game-loop.ts 文件
// init.ts
let cvs_one: HTMLCanvasElement,
cvs_two: HTMLCanvasElement,
ctx_one: CanvasRenderingContext2D,
ctx_two: CanvasRenderingContext2D;
let cvs_width: number,
cvs_height: number;
const bgPic = new Image();
function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
const dom = <HTMLCanvasElement>document.querySelector('#' + id);
const ctx = dom.getContext('2d');
return [dom, ctx];
}
function init() {
[cvs_one, ctx_one] = getCanvasAndContextById('one');
[cvs_two, ctx_two] = getCanvasAndContextById('two');
bgPic.src = 'assets/img/background.jpg';
cvs_width = cvs_one.width;
cvs_height = cvs_one.height;
}
export default init;
export { bgPic, cvs_width , cvs_height, ctx_two };
// game-loop.ts
import { bgPic, cvs_width , cvs_height, ctx_two } from "./init";
let lastTime: number = Date.now(), // 记录上一次绘制的时间
deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界
function gameLoop() {
const now = Date.now()
deltaTime = now - lastTime;
lastTime = now;
console.log(deltaTime);
drawBackbround()
requestAnimationFrame(gameLoop);
}
function drawBackbround() {
ctx_two.drawImage(bgPic, 0, 0, cvs_width, cvs_height)
}
export default gameLoop;
// main.ts
import gameLoop from "./game-loop";
import init from "./init";
window.onload = () => {
init();
gameLoop();
}
重构搞定!