Canvas реализует крутые цифровые эффекты дождя из The Matrix.

внешний интерфейс Vue.js NPM Canvas

мотивация

Недавно я пересмотрел «Матрицу» и обнаружил, что этот цифровой спецэффект дождя очень классный. Я также видел похожие коды в Интернете раньше. Сначала я сам подумал о методе реализации и, наконец, сослался на идею, представленную в Интернете, и наконец написал это как Плагин vue размещен на npm, а gif со спецэффектами будет добавлен ниже.


Это похоже на фильм, но все же немного отличается

собственный метод реализации (отказ)

Для таких сложных анимационных эффектов лучше всего подходит холст.Конечно, CSS тоже может это сделать, но это должно быть очень сложно, а объем кода огромен. Первое, что я увидел, увидев этот спецэффект, было вот это:
(1) Как правило, холст используется для рисования статических изображений. Поскольку этот пример представляет собой эффект анимации, необходимо вызвать setTimeout, setInterval или raf. Здесь raf используется для непрерывного рисования изображений для достижения динамических эффектов, и следует использовать raf, который лучше, чем setTimeout/ Смысл setInterval в том, что это API, предоставляемый браузером специально для анимации. Браузер автоматически оптимизирует вызов метода во время выполнения, и если страница не активна, анимация автоматически приостанавливается, эффективно сохраняя Накладные расходы ЦП.
(2) Нарисуйте черный фон
(3) Поскольку цифровой дождь кажется независимой последовательностью цифр и букв, движущихся вниз, мне нужно создать новый класс DigitRain, который устанавливает различные атрибуты цифрового дождя для управления характеристиками движения цифрового дождя. код ниже

    //数字雨类(参数是配置对象)
    function DigitRain(configObj){
        //数字雨的位置(x轴)
        this.digitRainXPos = configObj.digitRainXPos,
        //数字雨的位置(y轴)
        this.digitRainYPos = configObj.digitRainYPos,
        //数字雨的下落速度
        this.rainVelocity = configObj.rainVelocity,
        //数字雨的颜色
        this.rainColor = configObj.rainColor,
        //数字雨的拖尾长度
        this.rainTailLength = configObj.rainTailLength,
        //数字雨的文本内容
        this.rainText = configObj.rainText,
        ...
    }

(4) Затем напишите метод рисования для управления его движением и, наконец, вызовите fillText на холсте, чтобы нарисовать текст.

В конце концов, я некоторое время писал и обнаружил, что это слишком сложно, особенно обработка эффекта трейлинга текста была очень хлопотной, а эффект не был достигнут, поэтому я сдался.

передумай

Что касается типа мышления в Интернете, можно сказать, что этот тип мышления упрощен, и его легко понять, я должен восхищаться им.
(1) Raf также используется для достижения эффекта анимации.Во-первых, количество столбцов (ширина/размер шрифта), в которые падают капли дождя, рассчитывается в соответствии с шириной холста и размером шрифта, аrainDropArray(Длина - это количество столбцов) Запишите положение по оси Y текста каждого столбца, начальное значение равно 0, основная структура данных такова.rainDropArray
(2) requestAnimationFrameВ функции параметра используйте цикл for для обходаrainDropArray, затем используйтеfillTextНарисуйте текст на холсте, позиция по оси X — это индекс * размер шрифта массива, а позиция по оси Y —rainDropArray[i]Значение и каждый раз, когда fillText использует инкапсулированный случайный метод для получения случайных чисел и букв строки
(3) Обработка трейлинг-эффекта: Здесь очень умно.Для трейлинг-эффекта вам нужно толькоrequestAnimationFrameФункция параметраfillRect(0,0,.canvas.width,canvas.height)может быть, покаfillStyleУстановить какrgba(0,0,0,alpha), Таким образом, такой черный фон будет отображаться каждый раз, когда вы рисуете изображение, таким образом закрывая буквы, нарисованные ранее, делая цвет букв светлее для достижения эффекта замыкания и контролируя длину замыкания, контролируя значение альфа,Обратите внимание, что clearReact не используется для очистки последнего нарисованного содержимого при рисовании, и каждый раз, когда эффект, нарисованный в последний раз, накладывается

Как показано на картинке выше, хотя на картинке много букв, на самом делеrequestAnimationFrameКаждый раз рисуются только буквы в красном кружке, то есть буквы, соответствующие каждому столбцу, а остальные буквы с более светлыми цветами прорисовываются.requestAnimationFrameТот, что был нарисован ранее, покрыт только недавно нарисованным черным фоном и, таким образом, затемнен, что идеально обеспечивает эффект замыкания. (4) В началеrainDropArrayКаждое значение равно 0, и все столбцы падают с одинаковой скоростью, поэтому анимация начнется следующим образом.

Он падает аккуратно, поэтому его нужно обработать, когда буквы достигнут нижней части холста, чтобы они имели последовательный порядок.Код следующий.После удара о дно дается определенная вероятность сбросить ось y положение на 0, чтобы добиться кругового падения столбца.

 if(textYPostion>this.canvasHeight){
       if(Math.random()>0.9){
           this.rainDropPositionArray[i]=0;
       }
 }

Упакованный код подключаемого модуля Vue

Общий код не большой, менее 150 строк, шаблонная часть - это только канвас

<template>
    <canvas id="vue-matrix-raindrop"></canvas>
</template>

<script>
    export default {
    name: 'vue-matrix-raindrop',
    //插件的各种参数
    props:{
        //canvas宽度
        canvasWidth:{
            type:Number,
            default:800
        },
        //canvas高度
        canvasHeight:{
            type:Number,
            default:600
        },
        //下落字体大小
        fontSize:{
            type:Number,
            default:20
        },
        //字体类型
        fontFamily:{
            type:String,
            default:'arial'
        },
        //字体文本内容,会随机从字符串里取一个
        textContent:{
            type:String,
            default:'abcdefghijklmnopqrstuvwxyz'
        },
        //字体颜色
        textColor:{
            type:String,
            default:'#0F0',
            validator:function(value){
                 var colorReg = /^#([0-9a-fA-F]{6})|([0-9a-fA-F]{3})$/g
                 return colorReg.test(value)
            }
        },
        //canvas背景颜色,可自定义
        backgroundColor:{
            type:String,
            default:'rgba(0,0,0,0.1)',
            validator:function(value){
              var reg = /^[rR][gG][Bb][Aa][\(]((2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),){2}(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),?(0\.\d{1,2}|1|0)?[\)]{1}$/;
              return reg.test(value);
            }
        },
        //下落速度
        speed:{
            type:Number,
            default:2,
            validator:function(value){
              return value%1 === 0;
            }
        }
    },
    mounted:function(){
          this.initRAF();
          this.initCanvas();
          this.initRainDrop();
          this.animationUpdate();
    },
    methods:{
         //初始化requestAnitaionFrame,注意兼容性
    	 initRAF(){
             window.requestAnimationFrame = (function(){
               return window.requestAnimationFrame       ||
                      window.webkitRequestAnimationFrame ||
                      window.mozRequestAnimationFrame    ||
                      window.oRequestAnimationFrame      ||
                      function( callback ){
                        window.setTimeout(callback, 1000 / 60);
                      };
                })();
             window.cancelAnimationFrame = (function () {
               return window.cancelAnimationFrame ||
                      window.webkitCancelAnimationFrame ||
                      window.mozCancelAnimationFrame ||
                      window.oCancelAnimationFrame ||
                      function (id) {
                        window.clearTimeout(id);
                      };
               })();
        },
        //初始化canvas
    	initCanvas(){
             this.canvas = document.getElementById('vue-matrix-raindrop');
             //需要判断获取到的canvas是否是真的canvas
             if(this.canvas.tagName.toLowerCase() !== 'canvas'){
                console.error("Error! Invalid canvas! Please check the canvas's id!")
             }
             this.canvas.width = this.canvasWidth;
             this.canvas.height = this.canvasHeight;
             this.canvasCtx = this.canvas.getContext('2d');
             this.canvasCtx.font = this.fontSize+'px '+this.fontFamily;
             this.columns = this.canvas.width / this.fontSize;
       },
       //初始化数字雨下落的初始y轴位置
       initRainDrop(){
            for(var i=0;i<this.columns;i++){
                this.rainDropPositionArray.push(0);
            }
       },
       //核心动画函数,控制数字雨下落
       animationUpdate(){
         //控制雨滴下落的速度
       	 this.speedCnt++;
       	 //speed为1最快,越大越慢
       	 if(this.speedCnt===this.speed){
           this.speedCnt = 0;
           //绘制背景
           this.canvasCtx.fillStyle=this.backgroundColor;
           this.canvasCtx.fillRect(0,0,this.canvas.width,this.canvas.height);
           //绘制文字
           this.canvasCtx.fillStyle=this.textColor;
           //遍历每一列的数字雨,然后在canvas上绘制该数字字母
           for(var i=0,len=this.rainDropPositionArray.length;i<len;i++){
             this.rainDropPositionArray[i]++;
             var randomTextIndex = Math.floor(Math.random()*this.textContent.length);
             var randomText = this.textContent[randomTextIndex];
             var textYPostion = this.rainDropPositionArray[i]*this.fontSize;
             this.canvasCtx.fillText(randomText,i*this.fontSize,textYPostion);
             //数字雨触碰canvas底部则一定概率重新回到顶部继续下落
             if(textYPostion>this.canvasHeight){
               if(Math.random()>0.9){
                 this.rainDropPositionArray[i]=0;
               }
             }
           }
         }
         window.requestAnimationFrame(this.animationUpdate)
       }
    },
    data () {
    	return {
    	    canvasCtx:null,
            canvas:null,
            columns:0,
            rainDropPositionArray:[],
            speedCnt:0
    	}
    }
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

адресная точка githubздесь