用JS类封装粒子特效(二)
在上一讲,用JS类封装粒子特效(一),我们成功地创建了一个 Lizi 类,并通过实例化调用类生成了一个紫色正方形。本讲,我们将让这个正方形沿水平线方向来回移动。粒子 Lizi 类里,已经有了一个方法,creating,它负责生成粒子。要让粒子动起来,我们还需要添加另一个方法,moving :
//Lizi的类
class Lizi {
//构造函数
constructor(pa,size = 20) {
//属性代码
}
//生成粒子方法
creating() {
//生成粒子代码
}
//粒子运动
moving() {
//粒子运动代码
}
}
这个 moving 函数,和 creating 函数一样,都是自定义的、自命名的,和 creating 能创建粒子一样,moving 将能驱动粒子移动。
我们已知 Lizi 类构造函数里有一个 this.left 和 一个 this.top 属性,这两个属性在上一讲介绍过是用来存储粒子元素(this.ele)定位值的。试想,如果我们不断地改变 this.left 和 this.top 的值,方法是或加上、或减去一个特定值,是不是就可以驱动 this.ele 运动了呢?答案是肯定的,因为,粒子其实就是一个HTML元素,我们给它一个自定义元素名称,叫 li-zi,它和 div 一样拥有定位属性,在生成粒子函数 creating 里我们就已经使用了 left 和 top 两个 CSS 属性令生成的矩形(粒子)呆在指定的地方,我们如果可以让它呆的地方不断变化,它就是移动的。
根据上面的分析,我们需要一个特定值去改变 this.left,我们就将该特定值命名为 xstep(x表示水平方向,step是步伐之意),故此,Lizi 类的构造函数 constructor 更改如下:
//Lizi类里的构造函数
constructor(pa,size = 20) {
this.pa = pa;
this.size = size;
this.bg = 'purple';
this.left = 10;
this.top = 10;
this.xstep = 1;
this.ele = document.createElement('li-zi');
}
只是添加了一个属性,this.xstep,初始值为 1,此属性用于存储粒子水平方向移动的步幅,为正数时将向右移动,为负数时向左移动。
moving 自定义函数将给 this.left 加上 this.xstep:
moving() {
this.left += this.xstep;
}
运算符 += 是将右边的变量加到左边的变量并改变左边变量的值(它实际上等同于 this.left = this.left + this.xstep),this.left 加上 this.xstep 之后,值会变大或变小(取决于步幅变量是正值还是负值),当大到一定的程度,会触碰或超越宿主的右边边界,所以我们需要设计一个条件,让粒子一旦触碰或超越宿主的右边边缘,步幅的值立马变为负数,this.xstep = 1 就变为 this.xstep = -1,这样 this.left += this.xstep 就不是加 1 而是减 1 了,就会往回走;往回走也会突破宿主的左边边界,即 0,这又需要设计另一个条件,让粒子一旦触碰左边边缘,立刻将 this.xstep 变为 1,this.xstep = 1,这样粒子又往右走。函数里我们不必给 this.xstep 赋具体的正、负值,因为它的值可能在实例化的时候改变,我们换一种聪明的做法:this.xstep = - this.xstep,就是令其互为正负值,这是一劳永逸的,不用担心步幅值的具体设定。
所设定的条件,无非就是粒子是否触碰或超越了宿主元素的左边的边缘和右边的边缘,故此,可以拿 this.left 的值和左右两边的边界值做比较。边界,指粒子所在的宿主即粒子的父元素的上、右、下、左四个方向的边框,最左、最上为 0 ,最右、最下分别为宿主的宽度、高度。粒子(子元素)的 style.left 为 0 时,触碰了宿主(父元素)的左边边缘,粒子(子元素)的 style.left 为 宿主(父元素)的宽度减去粒子的宽度时,粒子触碰了宿主右边边缘。试看这个简单算法的表达语句:
this.left <= 0 → 粒子对象的左边值 小于等于 0 时,触碰或突破父元素左边边缘
this.left >= this.pa.offsetWidth - this.size → 粒子对象的左边值 大于等于 宿主宽度-粒子宽度 时,触碰或突破父元素右边边缘
依据上面的比较条件,Lizi类里的 moving 函数可以改写如下:
moving() {
this.left += this.xstep; //加步幅以改变 left 值
//判断是否超越左右边界,若是,令步幅值互为正负值(原来是正值则变为负值,原来是负值变为正值)
if (this.left <= 0 || this.left >= this.pa.offsetWidth - this.size) this.xstep = -this.xstep;
}
两个条件写在小括号里,中间用 || 隔开,表示“或者”(or)的意思,if(条件一 || 条件二) 的意思是,“如果条件一 或 条件二成立”,只要有一个成立。那么,就处理 this.xstep 的赋值问题,this.xstep = -this.xstep ,正负数互反。(如果两个条件没有一个成立,this.xstep 的变化就不会发生。)
this.left 只是记录了粒子对象的左边值,要驱动粒子移动,需要令粒子对象的 this.ele 即粒子的 HTML元素(li-zi元素) 改变其 CSS属性里 的 left 值,即 this.ele.style.left,写成:this.ele.style.left = this.left + 'px'; ,moving 函数进一步改写如下:
moving() {
this.left += this.xstep;
if(this.left <= 0 || this.left >= this.pa.offsetWidth - this.size) this.xstep = -this.xstep;
this.ele.style.left = this.left + 'px';
}
上面的 moving 方法是给粒子对象的左边值(this.left)+ 水平移动步幅(this.xstep),执行 moving 函数,粒子就水平移动一个 this.xstep 单位距离,但是并没有不停地移动。要达到持续移动,我们需要引入定时器持续调用 moving 函数,这里可以考虑 requestAnimationFrame API,这是利用显示器分辨率封装的定时器接口,渲染特效过程不额外消耗CPU资源,特效窗口不活动时暂停特效渲染,其语法为:
requestAnimationFrame(函数名);
在 JS 类里使用 requestAnimationFrame,我们需要给它绑定 this 指向才能正常工作,语句为:
requestAnimationFrame(this.函数名.bind(this));
因此,moving 函数更新为:
moving() {
this.left += this.xstep;
if(this.left <= 0 || this.left >= this.pa.offsetWidth - this.size) this.xstep = -this.xstep;
this.ele.style.left = this.left + 'px';
requestAnimationFrame(this.moving.bind(this));
}
requestAnimationFrame定时器置于函数体之内且调用该函数,这叫递归调用,达到不停运行该函数的目的。
我们还可以让粒子一旦创建立马运行 moving 函数,这只需要简单的操作,修改一下 creating 函数即可:
creating() {
this.ele.style.cssText = `
width: ${this.size}px;
height: ${this.size}px;
left: ${this.left}px;
top: ${this.top}px;
background: ${this.bg};
`;
this.pa.appendChild(this.ele);
this.moving();
}
最后,实例化 Lizi 类,令人激动的时刻立马到来:
let lz = new Lizi(mydiv,100); //创建实例化粒子
lz.creating(); //创建一个粒子
本帖最后由 马黑黑 于 2023-7-7 08:39 编辑 <br /><br /><style>
#mydiv {
marin: 20px auto;
width: 600px;
height: 300px;
border: 1px solid purple;
position: relative;
}
li-zi {
position: absolute;
}
</style>
<div id="mydiv"></div>
<script>
class Lizi {
constructor(pa,size = 20) {
this.pa = pa;
this.size = size;
this.bg = 'purple';
this.left = 10;
this.top = 10;
this.xstep = 1;
this.ele = document.createElement('li-zi');
}
creating() {
this.ele.style.cssText = `
width: ${this.size}px;
height: ${this.size}px;
left: ${this.left}px;
top: ${this.top}px;
background: ${this.bg};
`;
this.pa.appendChild(this.ele);
this.moving();
}
moving() {
this.left += this.xstep;
if(this.left <= 0 || this.left >= this.pa.offsetWidth - this.size) this.xstep = -this.xstep;
this.ele.style.left = this.left + 'px';
requestAnimationFrame(this.moving.bind(this));
}
}
let lz = new Lizi(mydiv,50); //实例化粒子
lz.creating(); //创建一个粒子
</script>
本帖最后由 马黑黑 于 2023-7-7 08:40 编辑
二楼效果完整代码
<style>
#mydiv {
marin: 20px auto;
width: 600px;
height: 300px;
border: 1px solid purple;
position: relative;
}
li-zi {
position: absolute;
}
</style>
<div id="mydiv"></div>
<script>
class Lizi {
constructor(pa,size = 20) {
this.pa = pa;
this.size = size;
this.bg = 'purple';
this.left = 10;
this.top = 10;
this.xstep = 1;
this.ele = document.createElement('li-zi');
}
creating() {
this.ele.style.cssText = `
width: ${this.size}px;
height: ${this.size}px;
left: ${this.left}px;
top: ${this.top}px;
background: ${this.bg};
`;
this.pa.appendChild(this.ele);
this.moving();
}
moving() {
this.left += this.xstep;
if(this.left <= 0 || this.left >= this.pa.offsetWidth - this.size) this.xstep = -this.xstep;
this.ele.style.left = this.left + 'px';
requestAnimationFrame(this.moving.bind(this));
}
}
let lz = new Lizi(mydiv,50); //实例化粒子
lz.creating(); //创建一个粒子
</script>
本帖最后由 马黑黑 于 2023-7-7 08:55 编辑
水平方向的移动解决了,那么,垂直方向的移动呢?
步骤一:给 constructor 构造函数添加一个属性
constructor(pa,size = 20) {
this.pa = pa;
this.size = size;
this.bg = 'purple';
this.left = 10;
this.top = 10;
this.xstep = 1;
this.ystep = 1;
this.ele = document.createElement('li-zi');
}
步骤二:将 moving 函数改写为——
moving() {
this.left += this.xstep;
if(this.top<= 0 || this.top >= this.pa.offsetHeight - this.size) this.ystep = -this.ystep;
this.ele.style.top = this.top + 'px';
}
本帖最后由 马黑黑 于 2023-7-7 09:00 编辑
水平方向和垂直方向同时移动的完整代码,大家可以复制代码去 pencil code (freeee.ml) 测试一下效果:
<style>
#mydiv {
marin: 20px auto;
width: 600px;
height: 300px;
border: 1px solid purple;
position: relative;
}
li-zi {
position: absolute;
}
</style>
<div id="mydiv"></div>
<script>
class Lizi {
constructor(pa,size = 20) {
this.pa = pa;
this.size = size;
this.bg = 'purple';
this.left = 10;
this.top = 10;
this.xstep = 1;
this.ystep = 1;
this.ele = document.createElement('li-zi');
}
creating() {
this.ele.style.cssText = `
width: ${this.size}px;
height: ${this.size}px;
left: ${this.left}px;
top: ${this.top}px;
background: ${this.bg};
`;
this.pa.appendChild(this.ele);
this.moving();
}
moving() {
this.left += this.xstep;
this.top += this.ystep;
if(this.left <= 0 || this.left >= this.pa.offsetWidth - this.size) this.xstep = -this.xstep;
if(this.top <= 0 || this.top >= this.pa.offsetHeight - this.size) this.ystep = -this.ystep;
this.ele.style.left = this.left + 'px';
this.ele.style.top = this.top + 'px';
requestAnimationFrame(this.moving.bind(this));
}
}
let lz = new Lizi(mydiv,50); //创建实例化粒子
lz.creating(); //创建一个粒子
</script>
本帖最后由 马黑黑 于 2023-7-7 09:22 编辑 <br /><br /><p>单击移动的大正方形,看看会发生什么:<br><br></p>
<div id="gamebox" style="width: 700px; height: 400px; border: 1px solid gray; position: relative; margin: 10px auto;"></div>
<script>
let pp = new Lizi(gamebox,100);
pp.creating();
let btnLz = document.querySelectorAll('li-zi');
btnLz.onclick = () => {
let p = new Lizi(gamebox,20)
p.top = 300;
p.creating();
}
</script> 马黑黑 发表于 2023-7-7 08:41
本帖最后由 马黑黑 于 2023-7-7 09:22 编辑 单击移动的大正方形,看看会发生什么:
我怎么点不了 把(一)(二)连起来看了一遍,自我感觉明白些了。。{:4_173:} 南无月 发表于 2023-7-7 09:30
我怎么点不了
6楼的大正方形可点,再点 南无月 发表于 2023-7-7 09:53
把(一)(二)连起来看了一遍,自我感觉明白些了。。
应该能明白什么 马黑黑 发表于 2023-7-7 10:27
6楼的大正方形可点,再点
我也点不了{:4_203:} 黑黑把其中的内容都揉碎了掰开了详细讲解,这样的讲解太好了{:4_199:} 感觉moving() 有点像JS,而前面的creating()等像css{:4_173:} 马黑黑 发表于 2023-7-7 10:27
6楼的大正方形可点,再点
刚才没发现。。。点一下生一个小的{:4_173:}点几个生几个小的,点快了两个小的会连在一起。。 马黑黑 发表于 2023-7-7 10:28
应该能明白什么
教程给的极明白,一词一句的讲。。接下来是消化。。。 南无月 发表于 2023-7-7 10:53
教程给的极明白,一词一句的讲。。接下来是消化。。。
{:4_181:} 南无月 发表于 2023-7-7 10:51
刚才没发现。。。点一下生一个小的点几个生几个小的,点快了两个小的会连在一起。。
没有设限,随便点,内存不消耗殆尽就行 红影 发表于 2023-7-7 10:40
我也点不了
不可能吧 红影 发表于 2023-7-7 10:43
黑黑把其中的内容都揉碎了掰开了详细讲解,这样的讲解太好了
希望能看懂 红影 发表于 2023-7-7 10:45
感觉moving() 有点像JS,而前面的creating()等像css
它们都是JS。JS去操纵 CSS 和 HTML,同时还能做复杂的运算,就这么回事。
46和47行,就是 moving 函数中操作 css 的属性值,前面的是计算,49行是调用了定时器。JS就是这么做事的:通过准备,然后去管 CSS 和 HTML 的东东。