马黑黑 发表于 2022-8-14 09:03

字符雨实现方法(兼解释黑客帝国帖子)

本帖最后由 马黑黑 于 2022-8-14 09:41 编辑

我们需要在 HTML5 画布 canvas 上模拟字符雨。帖子尺寸应受到帖子外框限制,所以设定必要的CSS:

<style>
      #papa { left: -214px; width: 1024px; height: 640px; background: black url('/data/attachment/forum/202208/14/074919yeeelef1ee9yj1en.jpg') no-repeat center/cover; box-shadow: 3px 3px 20px #000; position: relative; }
      #canv { position: absolute; opacity: .75; }
</style>


父框有背景图,所以给画布设置一点点透明度(opacity: .75),令背景图片隐约可见。

接着是HTML代码:

<div id="papa">
      <canvas id="canv" width="1024" height="640"></canvas>
</div>


HTML中,暂时没有其他元素,仅是父元素+画布这么简单的结构,以减少对核心代码理解的干扰。

下面是重点,JS部分。说明一下,下面的解释会用到两个变量,w、h,它们是画布的宽高,赋值方法在后面所附的帖子完整代码里可以找到。

首先解决输出的字符问题。第一,我们需要一个字符集合,用一个数组变量存储,它可以是酱紫:

let texts = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ').split('');

或更简单,反正只要有合适的字符集合就成。为了高逼格,这回我们把键盘上的可打印字符都用上,其实也不多吧,对应的ASCII码,十进制的32是空格,我们不要,就从33开始,然后最后一个是 ~,十进制编码是126,这样总共94个,可以用js内置方法 String.fromCharCode(编码) 获得字符:

let texts = Array.from(Array(94), (x,k) => String.fromCharCode(33+k));

上面语句,我们用JS高效的构建数组方法之一,快速建立了一个 texts 数组,根据ASCII对照表,它应该包含的字符是键盘常规键位上可打印输出的字符,总之比数字加字母的字符丰富,给马上营造出来的代码雨增添更多的字符样式。

第二,设计字符输出列。将来,随机出现的字符应该是自左向右依次排列开来,一列能有多少个字符需要计算,而这个计算又得依据单个字符的宽度,所以,我们还需要几个变量:

      let fontsize = 16;//设定字号
      let columns = Math.floor(w / fontsize) - 1; //计算所需列 :宽度除以字宽的列数,用Math.floor确保是整数,再减1(并配套后续的操作)让字符不顶边
      let drops = new Array(columns); //根据列数创建一个空 drops 数组,将来用于装载每个输出字符的相关信息


以上这些变量,在JS里,可以合起来一同声明,只用一个 let 关键字(声明的变量间用小角逗号隔开,可以分行写也可以不分行):

    let texts = Array.from(Array(94), (x,k) => String.fromCharCode(33+k)), //字符集
      fontsize = 16, //字号
      columns = Math.floor(w / fontsize) - 1, //列数
      drops = new Array(columns); //输出列中单字标识数组,存储输出行的单字标识


最后,我们编写一个立即执行函数,就大功告成:

(function draw(){
      ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';//一定透明度的填充色
      ctx.fillRect(0, 0, w, h); //用上面底色覆盖画布(以保留字符落下的痕迹,其实是拖尾效果的应用)
      //ctx.clearRect(0,0,w,h); //清空画布 测试用 :观察字符落下的行为等
      ctx.fillStyle = '#0f0'; //字符填充颜色
      ctx.font = fontsize + 'px arial'; //设定字体
       //下面用for语句根据drops数组处理每一个输出的字符
         for(var j = 0; j < drops.length; j ++){
                let text = texts; //随机从字符集合数组texts中获得输出字符
                ctx.fillText(text, j * fontsize + fontsize / 2 + 1, drops * fontsize); //该字符输出:X坐标和Y坐标均与字宽息息相关
                //下面设定两种可能:字符对应的 drops 数组元素如果大于 h(画布高度)或大于一个随机数,这两种可能,只要符
                //合其中一个,则令元素值为0重头再来,如此反复,从而形成字符雨的错落感                //字符高度如果大于画布高度 或 伪随机数大于 0.95 (Math.random() 取值在 0-0.99999... 之间)
                if(drops*fontsize > h || Math.random() > 0.95){
                        drops = 0;
                }
                drops++; //字符对应的数组元素值自增(其值初始时为空,++表示加1,空值加任何数空值就是数值,JS的宽松变量类型特性
      }
      requestAnimationFrame(draw); //请求动画帧
      //setTimeout(draw, 34); //如果需要控制代码雨下落速度,可以考虑使用这个替代请求动画帧
})();


【附】本机测试用的帖子完整代码

<style>
      #papa { margin: auto; width: 1024px; height: 640px; background: black url('pic/hacker.jpg') no-repeat center/cover; box-shadow: 3px 3px 20px #000; position: relative; }
      #canv { position: absolute; opacity: .75; }
      #disc { position: absolute; width: 40px; height: 40px; left: 10px; bottom: 10px; background: conic-gradient(red,orange,yellow,green,teal,blue,purple); mask: radial-gradient(transparent 4px,red 0); -webkit-mask: radial-gradient(transparent 4px,red 0); border-radius: 50%; cursor: pointer; animation: rot 2s linear infinite; }
      #lrcbox { position: absolute; left: 60px; bottom: 10px;font: bold 22px / 40px sans-serif; color: #859670; text-shadow: 2px 2px 4px #222; }
      @keyframes rot { to { transform: rotate(360deg); } }
</style>

<div id="papa">
      <span id="lrcbox">黑客帝国</span>
      <canvas id="canv" width="1024" height="640"></canvas>
      <span id="disc"></span>
</div>

<script type="text/javascript">

let ctx = canv.getContext('2d');
let w = canv.width, h = canv.height;
let texts = Array.from(Array(94), (x,k) => String.fromCharCode(33+k)),
      fontsize = 16,
      columns = Math.floor(w / fontsize) - 1,
      drops = new Array(columns),
      aud = new Audio();
      
aud.src = 'https://music.163.com/song/media/outer/url?id=1809135506.mp3';
aud.loop = true;
aud.autoplay = true;

disc.style.animationPlayState = aud.paused ? 'paused' : 'running';
disc.onclick = () => aud.paused ? aud.play() : aud.pause();
aud.addEventListener('playing',()=> disc.style.animationPlayState = 'running');
aud.addEventListener('pause',()=> disc.style.animationPlayState = 'paused');

(function draw(){
      ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
      ctx.fillRect(0, 0, w, h);
      ctx.fillStyle = '#0f0';
      ctx.font = fontsize + 'px arial';
         for(var j = 0; j < drops.length; j ++){
                let text = texts;
                ctx.fillText(text, j * fontsize + fontsize / 2 + 1, drops * fontsize);
                if(drops*fontsize > h || Math.random() > 0.95){
                        drops = 0;
                }
                drops++;
      }
      requestAnimationFrame(draw);
})();

</script>



红影 发表于 2022-8-14 10:55

原来不是竖排,而是不停地写横排,从而感觉像字符竖排了?

红影 发表于 2022-8-14 10:55

这个好难啊{:4_173:}

马黑黑 发表于 2022-8-14 11:10

红影 发表于 2022-8-14 10:55
原来不是竖排,而是不停地写横排,从而感觉像字符竖排了?

就像下雨一样,不同的是这里只有一排雨,然后这排雨的终点可能不同,从而错落开来,形成长短不一的雨条

马黑黑 发表于 2022-8-14 11:13

红影 发表于 2022-8-14 10:55
这个好难啊

知识点多一些,但:

一,生成字符集合可以用简化的东东代替;
二,高效生成数组的方法可以强行使用,不一定去记住,懂方法就好。

真正难的,是 drops 数组是如何跟输出的字符建立关系进而驱动它们下落。

小辣椒 发表于 2022-8-14 11:13

原来这个就是我以为的隐藏代码,已经编程好字符了

马黑黑 发表于 2022-8-14 11:15

小辣椒 发表于 2022-8-14 11:13
原来这个就是我以为的隐藏代码,已经编程好字符了

字符是要做准备的,不做的话,使用时生成也可以。

小辣椒 发表于 2022-8-14 11:18

马黑黑 发表于 2022-8-14 11:15
字符是要做准备的,不做的话,使用时生成也可以。

这个对我有难度的,字符可以自己随便敲上去?

加林森 发表于 2022-8-14 11:19

来学习。老黑讲得非常详细。谢谢老黑啦!请喝茶:{:4_190:}

马黑黑 发表于 2022-8-14 11:42

小辣椒 发表于 2022-8-14 11:18
这个对我有难度的,字符可以自己随便敲上去?

我不是介绍有的么

小辣椒 发表于 2022-8-14 11:48

马黑黑 发表于 2022-8-14 11:42
我不是介绍有的么

我看了,也是自己瞎捣鼓了{:4_170:}

红影 发表于 2022-8-14 12:08

马黑黑 发表于 2022-8-14 11:10
就像下雨一样,不同的是这里只有一排雨,然后这排雨的终点可能不同,从而错落开来,形成长短不一的雨条

我开始以为是把字符变成竖排的了,嗯,这些错落有致也很美{:4_187:}

红影 发表于 2022-8-14 12:10

马黑黑 发表于 2022-8-14 11:13
知识点多一些,但:

一,生成字符集合可以用简化的东东代替;


这个字符集以前一点不知道,还在奇怪为什么凭空就有字符出来了呢{:4_173:}

红影 发表于 2022-8-14 12:11

马黑黑 发表于 2022-8-14 11:13
知识点多一些,但:

一,生成字符集合可以用简化的东东代替;


驱动字符集更是难中之难了{:4_173:}

红影 发表于 2022-8-14 12:12

这个texts可以放入汉字么?

马黑黑 发表于 2022-8-14 12:21

红影 发表于 2022-8-14 12:12
这个texts可以放入汉字么?

啥字符都行

马黑黑 发表于 2022-8-14 12:22

红影 发表于 2022-8-14 12:11
驱动字符集更是难中之难了

字符也是一个对象,在CSS里,要用一个元素装载,在画布,它有输出方法,fillText和strokeText

马黑黑 发表于 2022-8-14 12:24

红影 发表于 2022-8-14 12:10
这个字符集以前一点不知道,还在奇怪为什么凭空就有字符出来了呢

其实我有说到字符集的,我还说过用几行代码输出全部GBK汉字

马黑黑 发表于 2022-8-14 12:26

红影 发表于 2022-8-14 12:08
我开始以为是把字符变成竖排的了,嗯,这些错落有致也很美

还行的

红影 发表于 2022-8-14 12:26

马黑黑 发表于 2022-8-14 12:21
啥字符都行

哈哈,那可以随便放什么字进去了,这个比较出乎意料呢,我一会去试试{:4_173:}
页: [1] 2 3
查看完整版本: 字符雨实现方法(兼解释黑客帝国帖子)