下面我把制作过程详细讲解一下。
生成canvas标签,设置canvas长宽
const canvas = document.createElement('canvas'); document.body.appendChild(canvas); canvas.width = window.innerWidth; canvas.height = window.innerHeight;
因为要将canvas设置成背景,所以要绝对定位canvas
const canvas = document.createElement('canvas'); canvas.style.position = 'absolute'; canvas.style.top = 0; canvas.style.left = 0; canvas.style.zIndex = -1; document.body.appendChild(canvas); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const c = canvas.getContext('2d');
先画个渐变色作为背景
//createLinearGradient的4个参数:startX,startY,EndX,EndY const backgroundGradient = c.createLinearGradient(0, 0, 0, canvas.height); backgroundGradient.addColorStop(0, 'rgba(23, 30, 38, 0.7)'); backgroundGradient.addColorStop(1, 'rgba(63, 88, 107, 0.7)'); c.fillStyle = backgroundGradient; c.fillRect(0, 0, canvas.width, canvas.height);
看下效果
画山要有几个设置参数:1.位置 2.颜色 3.画几个
从简单开始,我们将山的坐标点设在三角形的最左边那个角,先画三角形的底边,然后画上顶角
c.beginPath(); c.moveTo(0, canvas.height / 2); c.lineTo(300, canvas.height / 2); c.lineTo(150, canvas.height / 4); c.closePath(); c.fill();
接下去,我们想画连起来的山
显然这山画得...我们要想有那种峰峦叠嶂的感觉,那就必须把山脚和山脚之间重叠起来,像这样
首先,定义一个函数drawMountains(),要传入的参数:1、山的数量number,2、山的坐标y(坐标x直接根据山的数量计算出来,3、山的高度height,4、山的颜色color),5、重叠偏移量offset,好了就这些,让我们来实现它,go
这里来说一下x坐标如何计算,我们假设绘制出的山横跨整个canvas,那么每座山的宽度就是canvas.width / number
了,如果都按这个x坐标来绘制,结果就是画出没重叠的山。那么如何解决重叠问题呢?我们可以在绘制底边时给起点和终点加一个偏移量:在起点加一个负的偏移量,在终点加一个相同大小的正的偏移量。
function drawMountains(number, y, height, color, offset) { c.save(); c.fillStyle = color; const width = canvas.width / number; // 循环绘制 for (let i = 0; i < number; i++) { c.beginPath(); c.moveTo(width * i - offset, y); c.lineTo(width * i + width + offset, y); c.lineTo(width * i + width / 2, y - height); c.closePath(); c.fill(); } c.restore(); } drawMountains(3, canvas.height / 2, 200, '#384551', 100);
看下画成什么样了
Bingo!成功了。接着画剩下的山
drawMountains(1, canvas.height, canvas.height * 0.78, '#384551', 300); drawMountains(2, canvas.height, canvas.height * 0.64, '#2B3843', 400); drawMountains(3, canvas.height, canvas.height * 0.42, '#26333E', 150);
我们先从简单的开始,就只是在背景上绘制星空。这里的思路是:定义一个星空星星的类Skystar,然后通过循环创建出一堆的skystar,这些skystar实例拥有各自独立的位置和半径大小,最后将这些skystar一个个画到canvas上。Let's code!
定义Skystar及一些实例方法
function Skystar() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.color = '#ccc'; this.shadowColor = '#E3EAEF'; this.radius = Math.random() * 3; } Skystar.prototype.draw = function() { c.save(); c.beginPath(); c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false); c.shadowColor = this.shadowColor; c.shadowBlur = Math.random() * 10 + 10; c.shadowOffsetX = 0; c.shadowOffsetY = 0; c.fillStyle = this.color; c.fill(); c.closePath(); c.restore(); }; Skystar.prototype.update = function() { this.draw(); };
接着定义一个全局数组skystarsArray,通过循环将多个skystar实例存入数组
const skyStarsArray = []; // 星空星星数组 const skyStarsCount = 400; // 星空初始生成星星数量 function drawSkyStars() { for (let i = 0; i < skyStarsCount; i++) { skyStarsArray.push(new Skystar()); } } drawSkyStars(); skyStarsArray.forEach(skyStar => skyStar.update());
接着我们来思考下如何让星星动起来。这里定义一个animation函数,每一帧的绘制都在这个函数中进行,最后用requestAnimationFrame来重复调用animation函数,达到动画的效果。
背景星星要动起来,就要在每一帧根据星星的位置对星星进行重新绘制。每个星星的实例中,都定义了一个update方法,用来对星星的一些属性进行更新,所以我们现在修改update方法
const skyStarsVelocity = 0.1; // 星空平移速度 Skystar.prototype.update = function() { this.draw(); // 星空一直连续不断向右移 this.x += skyStarsVelocity; };
然后在animation中将之前画山和画星空的方法加进去
function animation() { requestAnimationFrame(animation); // 画背景 c.fillStyle = backgroundGradient; c.fillRect(0, 0, canvas.width, canvas.height); // 画星星 skyStarsArray.forEach(skyStar => skyStar.update()); // 画山 drawMountains(1, canvas.height, canvas.height * 0.78, '#384551', 300); drawMountains(2, canvas.height, canvas.height * 0.64, '#2B3843', 400); drawMountains(3, canvas.height, canvas.height * 0.42, '#26333E', 150); }
在此之前,还有一些初始化的工作,这里定义一个init函数,专门用来初始化一些数据
function init() { drawSkyStars(); // 初始化背景星星 }
完整代码:
const canvas = document.createElement('canvas'); canvas.style.position = 'absolute'; canvas.style.top = 0; canvas.style.left = 0; canvas.style.zIndex = -1; document.body.appendChild(canvas); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const c = canvas.getContext('2d'); const skyStarsArray = []; // 星空星星数组 const skyStarsCount = 400; // 星空初始生成星星数量 const skyStarsVelocity = 0.1; // 星空平移速度 const backgroundGradient = c.createLinearGradient(0, 0, 0, canvas.height); //4个参数:startX,startY,EndX,EndY backgroundGradient.addColorStop(0, 'rgba(23, 30, 38, 0.7)'); backgroundGradient.addColorStop(1, 'rgba(63, 88, 107, 0.7)'); function init() { drawSkyStars(); // 初始化背景星星 } // 画山 function drawMountains(number, y, height, color, offset) { c.save(); c.fillStyle = color; const width = canvas.width / number; // 循环绘制 for (let i = 0; i < number; i++) { c.beginPath(); c.moveTo(width * i - offset, y); c.lineTo(width * i + width + offset, y); c.lineTo(width * i + width / 2, y - height); c.closePath(); c.fill(); } c.restore(); } function Skystar() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.color = '#ccc'; this.shadowColor = '#E3EAEF'; this.radius = Math.random() * 3; } Skystar.prototype.draw = function() { c.save(); c.beginPath(); c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false); c.shadowColor = this.shadowColor; c.shadowBlur = Math.random() * 10 + 10; c.shadowOffsetX = 0; c.shadowOffsetY = 0; c.fillStyle = this.color; c.fill(); c.closePath(); c.restore(); }; Skystar.prototype.update = function() { this.draw(); // 星空一直连续不断向右移 this.x += skyStarsVelocity; }; function drawSkyStars() { for (let i = 0; i < skyStarsCount; i++) { skyStarsArray.push(new Skystar()); } } function animation() { requestAnimationFrame(animation); // 画背景 c.fillStyle = backgroundGradient; c.fillRect(0, 0, canvas.width, canvas.height); // 画星星 skyStarsArray.forEach(skyStar => skyStar.update()); // 画山 drawMountains(1, canvas.height, canvas.height * 0.78, '#384551', 300); drawMountains(2, canvas.height, canvas.height * 0.64, '#2B3843', 400); drawMountains(3, canvas.height, canvas.height * 0.42, '#26333E', 150); } init(); animation();
嗯,效果有了,可是...
修改Skystar构造函数:
function Skystar(x) { this.x = x || (Math.random() - 0.5) * 2 * canvas.width; this.y = Math.random() * canvas.height; this.color = '#ccc'; this.shadowColor = '#E3EAEF'; this.radius = Math.random() * 3; }
修改animation函数:
function animation() { ... // 画星星 skyStarsArray.forEach((skyStar, index) => { // 如果超出canvas,则去除这颗星星,在canvas左侧重新生成一颗 if (skyStar.x - skyStar.radius - 20 > canvas.width ) { skyStarsArray.splice(index, 1); skyStarsArray.push(new Skystar(-Math.random() * canvas.width)); return; } skyStar.update() }); ... }
修改Skystar的update方法:
Skystar.prototype.update = function() { this.draw(); // 星空一直连续不断向右移 this.x += skyStarsVelocity; // y方向上有一个从上到下的偏移量,这里用cos函数来表示,模拟地球自转时看到的星空 let angle = Math.PI / (canvas.width / skyStarsVelocity) * (this.x / skyStarsVelocity); this.y += this.x > 0 ? -Math.cos(angle) * 0.03 : 0; };
接下来,我们绘制掉到地板上的星星,首先,我们画一个地板,修改animation函数
function animation() { ... // 画山 drawMountains(1, canvas.height, canvas.height * 0.78, '#384551', 300); drawMountains(2, canvas.height, canvas.height * 0.64, '#2B3843', 400); drawMountains(3, canvas.height, canvas.height * 0.42, '#26333E', 150); // 画地面 c.fillStyle = '#182028'; c.fillRect(0, canvas.height * 0.85, canvas.width, canvas.height * 0.15); }
构造函数Star
function Star() { this.radius = Math.random() * 10 + 5; this.x = Math.random() * (canvas.width - this.radius * 2) + this.radius; this.y = -Math.random() * canvas.height; this.velocity = { x: (Math.random() - 0.5) * 20, y: 5, rotate: 5 }; this.rotate = Math.sign(this.velocity.x) * Math.random() * Math.PI * 2; this.friction = 0.7; this.gravity = 0.5; this.opacit