马黑黑 发表于 2023-7-8 12:33

用JS类封装粒子特效(三)

本帖最后由 马黑黑 于 2023-7-8 13:01 编辑

上一讲 用JS类封装粒子特效(二)的 5楼 代码能驱使紫色正方形在父元素内上下左右来回移动。我们先来复习一下 Lizi 类完整代码,然后通过实例化配置实现真正的粒子特效:

class Lizi {
      //构造函数 : 属性设计、设置 pa - 粒子宿主元素,size - 粒子大小
      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'); //粒子元素 HTML标签名为 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));
      }
}

重点在:

① 构造函数里设计了 xstep 和 ystep 两个属性用以储存 x 和 y 方向的行进步幅;
② creating 函数调用了 moving 函数,粒子一旦创建立马动起来;
③ moving 函数驱使粒子运动,它通过改变粒子对象的 left 和 top 两个值,并对应去更改粒子元素属性 ele 的 CSS 的 left(style.left)和 top(style.top)属性达成运动目的,其中有边界判断、根据判断改变xy行进步幅的正负值,最后通过 requestAnimationFrame 定时器做永恒动力。

这样,实例化调用这个 Lizi 类可以是非常简单的:

let lz = new Lizi(mydiv,30); //实例化粒子
lz.creating(); //创建一个粒子

本讲,我们要创建更多的粒子,这些粒子是圆形的(在CSS中设置),它们的尺寸、位置、颜色以及移动步幅都不尽相同,需要一一配置。让我们在实例化类对象中完成这一操作:

//设置一组步幅数组 :以便令粒子的运动速度有差异性
let stepAr = ;

//创建30个粒子
Array.from({length: 30}).forEach((element) => {
      //随机获取步幅索引
      let xIdx = Math.floor(Math.random() * stepAr.length),
                yIdx = Math.floor(Math.random() * stepAr.length);
      element = new Lizi(mydiv); //实例化Lizi类
      //尺寸区间 :4 - 14
      element.size = 4 + Math.ceil(Math.random() * 10);
      //左边值
      element.left = Math.floor(Math.random() * (mydiv.offsetWidth - element.size));
      //上边值
      element.top = Math.floor(Math.random() * (mydiv.offsetHeight - element.size));
      //x步幅
      element.xstep = stepAr;
      //y步幅
      element.ystep = stepAr;
      //随机背景色 :十六进制颜色
      element.bg = '#' + Math.random().toString(16).substr(-6);
      //创建粒子
      element.creating();
});

粒子的形状我们可以在CSS的 li-zi 选择器中设置,border-radius: 50%; ,当然也可以在JS实例化的 creating() 函数执行后加入一句:element.ele.style.borderRadius = '50'; 。

这里,实例化代码看上去好复杂,实则逻辑清晰,都是对应 Lizi 类的属性进行相关属性配置、调用类提供的方法。在配置类实例化属性时,我们用上了 JS 的内置对象 Math,它提供丰富的数学操作,比如,我们用 Math.random() 生成随机数并用到各种算法中,用 Math.floor 和 Math.ceil 向下、向上获取整数,等等,还有其他的 JS 内置的东东,相关知识请参阅 JS:随机生成十六进制颜色值 - 马黑黑教程专版 - 花潮论坛 - Powered by Discuz! (huachaowang.com) (2楼)。

以下是本讲示例的完整代码:

<style>
#mydiv {
      margin: 20px auto;
      width: 600px;
         height: 300px;
      border: 1px solid purple;
      position: relative;
}
li-zi {
      position: absolute;
      border-radius: 50%;
}
</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 stepAr = ; //步幅数组

Array.from({length: 30}).forEach((element) => {
      let xIdx = Math.floor(Math.random() * stepAr.length),
                yIdx = Math.floor(Math.random() * stepAr.length);
      element = new Lizi(mydiv);
      element.size = 4 + Math.ceil(Math.random() * 10);
      element.left = Math.floor(Math.random() * (mydiv.offsetWidth - element.size));
      element.top = Math.floor(Math.random() * (mydiv.offsetHeight - element.size));
      element.xstep = stepAr;
      element.ystep = stepAr;
      element.bg = '#' + Math.random().toString(16).substr(-6);
      element.creating();
});

</script>运行效果请看下一楼。

马黑黑 发表于 2023-7-8 12:33

<style>
#mydiv {
        margin: 20px auto;
        width: 600px;
        height: 300px;
        border: 1px solid purple;
        position: relative;
}
li-zi {
        position: absolute;
        border-radius: 50%;
}
</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 stepAr = ; //步幅数组

Array.from({length: 30}).forEach((element) => {
        let xIdx = Math.floor(Math.random() * stepAr.length),
                yIdx = Math.floor(Math.random() * stepAr.length);
        element = new Lizi(mydiv);
        element.size = 4 + Math.ceil(Math.random() * 10);
        element.left = Math.floor(Math.random() * (mydiv.offsetWidth - element.size));
        element.top = Math.floor(Math.random() * (mydiv.offsetHeight - element.size));
        element.xstep = stepAr;
        element.ystep = stepAr;
        element.bg = '#' + Math.random().toString(16).substr(-6);
        element.creating();
});

</script>

南无月 发表于 2023-7-8 13:43

一粒到多粒,到颜色、位置、步幅随机变,老师逐字逐句细细讲解,极明白的教程。。。
{:4_190:}上杯茶给老师喝。。。

醉美水芙蓉 发表于 2023-7-8 14:04

红影 发表于 2023-7-8 15:12

这个单独列出来挺好,可以单独用到其他播放器效果的帖子里了{:4_187:}

红影 发表于 2023-7-8 15:18

步幅取了这么多,取少点也可以吧。
let xIdx = Math.floor(Math.random() * stepAr.length),这句具体的含义没看懂{:5_102:}stepAr.length好像不是里面的取值,而是数组长度?

红影 发表于 2023-7-8 15:26

这些粒子是圆形的(在CSS中设置)
这个能在css中设置,其他的好像不能?我去掉圆形加上转动,结果它不转呢。

红影 发表于 2023-7-8 15:32

才发现这个li-zi { }前面没有点啊#什么的,这样也可以啊{:4_173:}

焱鑫磊 发表于 2023-7-8 16:13

学习!{:4_187:}

马黑黑 发表于 2023-7-8 16:16

焱鑫磊 发表于 2023-7-8 16:13
学习!

{:4_190:}

马黑黑 发表于 2023-7-8 16:27

南无月 发表于 2023-7-8 13:43
一粒到多粒,到颜色、位置、步幅随机变,老师逐字逐句细细讲解,极明白的教程。。。
上杯茶给老 ...

谢茶

马黑黑 发表于 2023-7-8 16:32

红影 发表于 2023-7-8 15:32
才发现这个li-zi { }前面没有点啊#什么的,这样也可以啊
你好像翘过课?我在最近做的时钟中,那个完美版,用的就是自定义HTML标签,它的命名有个规范,用字母命名,分两部分,中间有连接符 - 连结起来。在 CSS 里,选择器这么写:

li-zi { ... }

css-doodle { ... }

在 HTML 里,标签这么写:

<li-zi> ... </li-zi>

<css-doodle> ... </css-doodle>

li-zi 也好,css-doodle 也好,都是自定义标签,浏览器都支持。一般来说,自定义标签自然拥有 div 的一切属性。

马黑黑 发表于 2023-7-8 16:50

本帖最后由 马黑黑 于 2023-7-8 16:52 编辑

红影 发表于 2023-7-8 15:18
步幅取了这么多,取少点也可以吧。
let xIdx = Math.floor(Math.random() * stepAr.length),这句具体的含 ...
取多取少取决于你对粒子运行速度的差异性的要求。一般来说,至少要有两个,一正一负,以保证粒子运行方向有上下左右这四个:

let stepAr = ;

然后,每一个粒子的 xstep 和 ystep 随机取这两个值,正正、正负、正负、负正、负负四个组合都可能存在。

至于 xIdx 和 yIdx,它们通过 Math 获得随机索引,分析如下:

stepAr.length 是数组 stepAr 的长度,即,有多少个数组元素;
Math.random() 是获得 0 - 0.999... 之间的随机数(浮点数);

Math.random() * stepAr.length 得到的数字也是浮点数,我们需要对之取整:

Math.floor(number) 是向下取整,如果 number 为 0.999...,则向下取整后是 0,如果 number 是 1.999... ,向下取整后得到的是 1;

现在,把 Math.random() * stepAr.length 替换 number,就是对 Math.random() * stepAr.length 向下取整,假设数组元素为 10 个,则可能得到的最小值是 0 和 0.n 之间,向下取整后是 0 、最大值是 9.999...,向下取整后是 9,正好对应最后一个数组元素的索引(数组索引从 0 开始)。

马黑黑 发表于 2023-7-8 16:56

红影 发表于 2023-7-8 15:26
这些粒子是圆形的(在CSS中设置)
这个能在css中设置,其他的好像不能?我去掉圆形加上转动,结果它不转呢 ...

那应该是你的设计有问题。试下这么设置CSS:

<style>
#mydiv {
      margin: 20px auto;
      width: 600px;
         height: 300px;
      border: 1px solid purple;
      position: relative;
}
li-zi {
      position: absolute;
        animation: rot 6s infinite linear;
}
@keyframes rot { to { transform: rotate(360deg); } }
</style>

樵歌 发表于 2023-7-8 19:33

也只能看一眼,这研究太高深了

马黑黑 发表于 2023-7-8 19:39

樵歌 发表于 2023-7-8 19:33
也只能看一眼,这研究太高深了

高度不到一米

南无月 发表于 2023-7-8 19:45

马黑黑 发表于 2023-7-8 16:27
谢茶

不用客气一下应该的

马黑黑 发表于 2023-7-8 19:52

南无月 发表于 2023-7-8 19:45
不用客气一下应该的

{:4_189:}

红影 发表于 2023-7-8 20:24

马黑黑 发表于 2023-7-8 16:32
你好像翘过课?我在最近做的时钟中,那个完美版,用的就是自定义HTML标签,它的命名有个规范,用字母命名 ...

哦,自定义标签是不用管这个的。学过呀,忘了呗。{:4_173:}

马黑黑 发表于 2023-7-8 20:25

红影 发表于 2023-7-8 20:24
哦,自定义标签是不用管这个的。学过呀,忘了呗。

脸没混熟
页: [1] 2 3 4 5 6
查看完整版本: 用JS类封装粒子特效(三)