本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
大家好我是ndz,很高兴也很荣幸成为了一名稀土掘金技术社区签约作者,在这里真的很感谢平台给予的肯定和各位读者的支持,感谢 🙏 🙏 🙏。
本文为稀土掘金技术社区签约作者专栏 - 从Canvas到PixiJs 的第一篇文章,喜欢的小伙伴记得点赞加关注,以防需要用时回来不迷路 😂
前言
学习成长的路上本就孤单,在之后的路上也希望大家多多支持和陪伴,我会尽我所能产出一些对大家有益的文章,在成长的路上我们一起做大做强😂。
因为是工作之余才有时间写文章,所以在公司项目不赶的情况下我会一边学习一边写文,但有时也难免公司项目很赶也会忙一段时间,就像这段时间,我已经有接近两个月没有写文章了,这段时间公司项目倒排真的是忙的飞起。
开始写文章之初其实是想对一些自己不熟的知识点做一些复习和总结,当然也会总结一些我正在学习的知识点,因此不写文章基本就代表着我没有在主动的推进自己的学习进度。当然这里并不是说忙项目就不能学到东西,我只是把学习成长归为两类,一类是我感兴趣去学的属于主动学习,一类是我需要去学的属于被动去学。举个例子,比如PixiJs就是我觉得很有意思主动去学习的,ReactJs就是我换工作公司为了项目统一都用的ReactJs所以被动去学的。扯远了~ 咱们回归正题。
和PixiJs的相遇
PixiJs的偶遇是我在北京的第一家公司,它是一个广告公司,当时公司在一个外包的项目中动画的性能没有达到甲方的预期,在尝试了我所知道的所有优化点以后还是偶尔会有卡顿的现象,最终虽然交了差但这个事却让我耿耿于怀。
后来公司来了一个大佬,他带我走进了PixiJs的世界。那是因为公司项目相对来说比较单一,所以我们用PixiJs封装了一个简单框架,用来做所有项目的底层架构,然后在之后的开发中不断的去完善各种基于PixiJs API的组件。也是从那时起我看到了一些DOM渲染的一些缺点:效率低,解析速度慢,内存占用量过高 性能差
随着和PixiJs的接触越多越觉得他的强大。尤其是在对网页性能要求越来越高的今天,项目性能优化已经成了项目开发中必不可少的一个环节。尤其是现在越来越火🔥的在线教育、在线编辑、直播、游戏等类型的项目中Canvas和WebGL的运用越来越多,而PixiJs就是一个HTML5创建引擎,是一个最快、最灵活的 2D WebGL 渲染器。
为什么有这个专栏?
至于写这个专栏的原因其实有很多,除了上面👆🏻说的兴趣以外在公司最近的项目中也打算用PixiJs来写一个项目,因此这是兴趣与需求的结合。还有就是在我以往的文章中PixiJs内容的文章下大家催更文章最多,最后一点就是掘金官方给的这一次签约机会,因为这些原因所以《从Canvas到Pixi.js》这个专栏应运而生成为了本期签约作者专栏。
综上这些原因我打算以复习和学习的思路整理一下我学习PixiJs的历程和我接下来在项目中遇到的PixiJs知识点以及一些我觉得有用的东西。
专栏简介
下面介绍一下在本专栏中你能学到的东西。
因为PixiJs的基础是Canvas,所以我会先从Canvas的基础说起,以免直接介绍PixiJs导致部分读者接受起来比较费劲,其次就是Canvas的应用,学会了基础以后会使用才能应用到项目中,所以在Canvas的基础之后就是Canvas的应用案例,随后才是PixiJs的学习,当然熟悉Canvas的小伙伴也可以跳过Canvas基础篇,直接进入PixiJs篇进行阅读,同样PixiJs也是从基础入手,然后再到案例,最终的效果就是实现一个图形化编辑器,大概的内容有:
- 使用PixiJs渲染元素,把需要的元素渲染到页面上
- 使用PixiJs编辑元素,对渲染到页面上的元素进行编辑,例如:移动、缩放、旋转、裁剪等
- 使用PixiJs对元素添加动画,让页面更灵动
大概内容如下图👇
当然这只是前期的预想,之后内容丰富了又会添加新的东西。因为文章不能直接贴视频,所以转了一个GIF图,但因为内容太大所以压缩的比较狠,想了解高清内容的可以点击 PixiJs专栏视频简介 查看高清视频。
结语
后面我们将开始从Canvas的基础开始,但因为之前我已经写过一篇Canvas的基础文章 案例+图解带你一文读懂Canvas🔥🔥(2W+字) 所以后面的基础文章会以总结的形式来展开。
具体内容如下:
介于后面要从Canvas的基础开始讲解,内容比较乏味,所以这里咱们先预热一个酷炫的案例,在绽放的烟花中走进我们的学习专栏吧 ~
配音:咻~ 嘣! 咻~ 嘣!咻~ 嘣!咻~ 咻~ 咻~ 咻~ 嘣!嘣!嘣!嘣!
效果如下:
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hi Canvas</title>
<style>
body {
margin: 0;
background: black;
}
canvas {
position: absolute;
}
</style>
</head>
<body>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<script src="./index.js"></script>
</body>
</html>
// 初始化
const [c1, c2, c3] = document.querySelectorAll('canvas');
const [ctx1, ctx2, ctx3] = [c1, c2, c3].map(c => c.getContext('2d'));
let fontSize = 200;
const rockets = [];
const shards = [];
const targets = [];
const fidelity = 3;
let counter = 0;
c2.width = c3.width = window.innerWidth;
c2.height = c3.height = window.innerHeight;
ctx1.fillStyle = '#000000';
const text = 'Hi Canvas';
let textWidth = 999999999;
while (textWidth > window.innerWidth) {
ctx1.font = `900 ${fontSize--}px Arial`;
textWidth = ctx1.measureText(text).width;
}
c1.width = textWidth;
c1.height = fontSize * 1.5;
ctx1.font = `900 ${fontSize}px Arial`;
ctx1.fillText(text, 0, fontSize);
const imgData = ctx1.getImageData(0, 0, c1.width, c1.height);
for (let i = 0, max = imgData.data.length; i < max; i += 4) {
const alpha = imgData.data[i + 3];
const x = Math.floor(i / 4) % imgData.width;
const y = Math.floor(i / 4 / imgData.width);
if (alpha && x % fidelity === 0 && y % fidelity === 0) {
targets.push({ x, y });
}
}
ctx3.fillStyle = '#ffffff';
ctx3.shadowColor = '#cccccc';
ctx3.shadowBlur = 5;
// 动画循环
(function loop() {
ctx2.fillStyle = "rgba(0, 0, 0, .1)";
ctx2.fillRect(0, 0, c2.width, c2.height);
// 不需要清空,否则烟花没有拖尾效果
// ctx2.clearRect(0, 0, c2.width, c2.height);
counter += 1;
if (counter % 15 === 0) {
rockets.push(new Rocket());
}
rockets.forEach((r, i) => {
r.draw();
r.update();
if (r.ySpeed > 0) {
r.explode();
rockets.splice(i, 1);
}
});
shards.forEach((s, i) => {
s.draw();
s.update();
if (s.timer >= s.ttl || s.lightness >= 99) {
ctx3.fillRect(s.target.x, s.target.y, fidelity + 1, fidelity + 1);
shards.splice(i, 1);
}
});
requestAnimationFrame(loop);
})();
// 辅助功能
const lerp = (a, b, t) => Math.abs(b - a) > 0.1 ? a + t * (b - a) : b;
// 获取目标
function getTarget() {
if (targets.length > 0) {
const idx = Math.floor(Math.random() * targets.length);
let { x, y } = targets[idx];
targets.splice(idx, 1);
x += c2.width / 2 - textWidth / 2;
y += c2.height / 2 - fontSize / 2;
return { x, y };
}
}
// 烟花碎片
class Shard {
constructor(x, y, hue) {
this.x = x;
this.y = y;
this.hue = hue;
this.lightness = 50;
this.size = 10 + Math.random() * 10;
const angle = Math.random() * 2 * Math.PI;
const blastSpeed = 1 + Math.random() * 6;
this.xSpeed = Math.cos(angle) * blastSpeed;
this.ySpeed = Math.sin(angle) * blastSpeed;
this.target = getTarget();
this.ttl = 100;
this.timer = 0;
}
draw() {
ctx2.fillStyle = `hsl(${this.hue}, 100%, ${this.lightness}%)`;
ctx2.beginPath();
ctx2.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx2.closePath();
ctx2.fill();
}
update() {
if (this.target) {
const dx = this.target.x - this.x;
const dy = this.target.y - this.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const a = Math.atan2(dy, dx);
const tx = Math.cos(a) * 5;
const ty = Math.sin(a) * 5;
this.size = lerp(this.size, 1.5, 0.05);
if (dist < 5) {
this.lightness = lerp(this.lightness, 100, 0.01);
this.xSpeed = this.ySpeed = 0;
this.x = lerp(this.x, this.target.x + fidelity / 2, 0.05);
this.y = lerp(this.y, this.target.y + fidelity / 2, 0.05);
this.timer += 1;
} else
if (dist < 10) {
this.lightness = lerp(this.lightness, 100, 0.01);
this.xSpeed = lerp(this.xSpeed, tx, 0.1);
this.ySpeed = lerp(this.ySpeed, ty, 0.1);
this.timer += 1;
} else
{
this.xSpeed = lerp(this.xSpeed, tx, 0.02);
this.ySpeed = lerp(this.ySpeed, ty, 0.02);
}
} else
{
this.ySpeed += 0.05;
// this.xSpeed = lerp(this.xSpeed, 0, 0.1);
this.size = lerp(this.size, 1, 0.05);
if (this.y > c2.height) {
shards.forEach((shard, idx) => {
if (shard === this) {
shards.splice(idx, 1);
}
});
}
}
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
}}
// 烟花火箭
class Rocket {
constructor() {
const quarterW = c2.width / 4;
this.x = quarterW + Math.random() * (c2.width - quarterW);
this.y = c2.height - 15;
this.angle = Math.random() * Math.PI / 4 - Math.PI / 6;
this.blastSpeed = 6 + Math.random() * 7;
this.shardCount = 15 + Math.floor(Math.random() * 15);
this.xSpeed = Math.sin(this.angle) * this.blastSpeed;
this.ySpeed = -Math.cos(this.angle) * this.blastSpeed;
this.hue = Math.floor(Math.random() * 360);
this.trail = [];
}
draw() {
ctx2.save();
ctx2.translate(this.x, this.y);
ctx2.rotate(Math.atan2(this.ySpeed, this.xSpeed) + Math.PI / 2);
ctx2.fillStyle = `hsl(${this.hue}, 100%, 50%)`;
ctx2.fillRect(0, 0, 5, 15);
ctx2.restore();
}
update() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
this.ySpeed += 0.1;
}
explode() {
for (let i = 0; i < 70; i++) {
shards.push(new Shard(this.x, this.y, this.hue));
}
}
}
PS: 案例来源于网络。