Decryption animation in JavaScript

Decryption animation in JavaScript

Β·

9 min read

So I just saw a video of hyperplexed creating an awesome Hacker effect. (codepen) and I thought how can I improve it.

First thing, the animation only runs on mouse over, and it's good for hoverable elements, but to use this animation on a showcasing element, it should run for infinite times.

Second, the text is revealed from left to right and that's good, but random reveal will be awesome.

One word is cool, but you know what's cooler, 2 words, 3 words, let's just say multiple words.

Our objectives are to :

  • Make this effect run forever

  • Make it Random reveal

  • Use multiple words

Check out Hyperplexed's video first to get better understanding

Let's cook...

Clear all the code that we copied from hyperplexed's codePen.

Define a selector for our HTML element.

// element selection
const element = document.querySelector("#h1")

Create a string of characters. We will use this to get random letters.

// characters for randomizations
const chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%&|?></\\";

This step is optional, if you want to have the direct access to the dataset of the element

// dataset selection
let dataSet;
if (!element.getAttribute("data-value")) {
element.setAttribute("data-value", "xivLogBook");
}
dataSet = element.dataset;

The above code checks whether the element has an attribute data-value or not, if not then set the attribute and the value of attribute to "xivLogBook".

Set the text of the element to the value of data-set

// set inner Text to data-value
element.innerText = dataSet.value

Since we are going to generate a lot of random numbers, let's just create a function for it.

// function that generates random number of given lenghts
function randomNumber(length) {
return Math.floor(Math.random()*length)
}

We have completed the necessary setup for further going.

0. Initial Hacker Effect setup

Logic taken from hyperplexed's video

For this objective, we will utilize the JavaScript built-in method setinterval.

let interval = setInterval(()=>{
element.innerText = element.innerText
    .split("")
    .map((letter, index) => {
        return chars[randomNumber(chars.length)];
    })
    .join("");
},30)

The above code will run every 30 millisecond and will map each letter to some random character, thus returning a new random word.

Put the above code in a new function and name it hackerEffect

function HackerEffect() {
    let interval = setInterval(() => {
        element.innerText = element.innerText
        .split("")
        .map((letter, index) => {
            return chars[randomNumber(chars.length)];
        })
        .join("");
    }, 30);
}

This code will only give some random word, not the original word.

To get the original word, add this code. The hackerEffect should look like this

function hackerEffect() {
    let iterator = 0 //new 1
    let interval = setInterval(() => {
        element.innerText = element.innerText
        .split("")
        .map((letter, index) => {
            if (iterator > index) { //new 2
                return dataSet.value[index] //new 3
            }
        return chars[randomNumber(chars.length)];
        })
        .join("");
        iterator += 1/2 //new 4
    }, 30);
}

1 -> initialized an iterator variable,

2 -> checking iterator is greater than index. (for better understanding watch this)

3 -> If the above condition is true then return original letter, else return random letter.

4 -> Increasing the value of iterator.

We want to stop after the original letter is revealed,for this add code inside the interval function

function hackerEffect() {
    let iterator = 0
    let interval = setInterval(() => {
        element.innerText = element.innerText
        .split("")
        .map((letter, index) => {
            if (iterator > index) {
                return dataSet.value[index]
            }
        return chars[randomNumber(chars.length)];
        })
        .join("");
        iterator += 1/2 //new
        if (iterator === dataSet.value.length) { //new
            clearInterval(interval) //new
        }
    }, 30);
}

The new code will check whether the original word is obtained or not. If yes, then it will clear the interval.

By far, we have done nothing new and all the logic is taken from the video.

Now let's first focus on how we can reveal word randomly.

1. Reveal a word randomly

For this purpose we will use the same revealing logic but rather than comparing the iterator to the index we will compare the iterator to some random number.

The random number should not be greater than the length of the word else the original word will not be revealed, and the code will generate random words

Rather than hard coding some arbitrary value, create an array that will store random numbers between 0 to the length of the original word. But if we store these numbers in ascending or descending order, our objective will not be achieved.

For our objective, we must store these numbers in random fashion.

function randomArray(length) {
    let randomArray = []
    let tmp = randomNumber(length)
    while (true) {
        if (!randomArray.includes(tmp)) {
            randomArray.push(tmp)
        } else {
            tmp = randomNumber(length)
        }
        if (randomArray.length >= length) {
            break
        }
    }
    return randomArray
}

Let's discuss what this code will do.

We created a function that will generate random array between 0 to provided length. First let tmp = randomNumber(length) will give us a random number

The while loop will run until we achieved the required length

The if (!randomArray.includes(tmp)) will check if the tmp value exists in array or not, if not then add the value in the array else generate new random number.

The above function will return an array of random numbers between 0 to provided length.

Let's generate an array of arbitrary values inside hackerEffect, by adding the following code.

...
    let arbitraryValues = randomValues(dataSet.value.length)
...

Now replace the index to arbitaryArray[index]

...
        .map((letter, index) => {
            if (iterator >= arbitraryValue[index]) { //new 2
                return dataSet.value[index] //new 3
            }
            ...

This code will reveal word in an arbitrary fashion.

2. Make it run forever

Javascript provides two methods for this purpose, setTimeout and setinterval. We are going to use setTimeout since it provides more precise control than setinterval (read this for better understanding of setTImeout vs setinterval)

let timeOut = setTimeout(function script() {
    hackerEffect()
    timeOut = setTimeout(script,3000)
},1000) //[1]

The above code will run for every 3 seconds. With the initial delay of 1 second.

[1] If you want to run this code as soon as window is loaded, then remove

this 1000.

Did you notice an odd behavior, the speed of characters is increasing rapidly? It is a bug caused by our interval.

2.1 setinterval Bug

To resolve this bug, Let's modify the interval in the hackerEfect. Create an interval variable in main document, i.e. outside the hackerEffect and assign null value to it.

let interval = null
function hackerEffect(...){
...
}

Now inside the hackerEffect clear this interval using clearInterval function at the very start of the function.

function hackerEffect(){
    clearInterval(interval)
    ...
}

This will make sure that every time hackerEffect runs, the previous interval is cleared before starting a new interval.

This code should run without some weird behavior for infinite times

let timeOut = setTimeout(function script() {
    hackerEffect()
    timeOut = setTimeout(script,3000)
},1000)

Play with different values of delays to make sure original word is revealed perfectly. ^speed

Our second objective is achieved. Let's go further...

3. Revealing multiple words

have you hear anything about typedjs? This library adds typing animation to a given word/sentences. We will do the exact but with hackerEffect animation.

First create an array that contains our desired words.

let words = ["Hello", "From", "Hacker", "Effect", "Improved"]

Now, create a variable index = 0.

let index = 0

Inside timeout, set the value of dataSet and the value of inner-Text to words[index]

let timeOut = setTimeout(function script() {
    // set data value to the words of given index
    dataSet.value = words[index]

    // set inner Text to value of data-set
    element.innerText = dataSet.value

    hackerEffect()
    timeOut = setTimeout(script,3000)
},1000)

This will only generate the first word in the array,

To make it generate all the words in the array increase the value of index gradually after setting the new inner-Text.

let timeOut = setTimeout(function script() {
    // set data value to the words of given index
    dataSet.value = words[index]

    // set inner Text to value of data-set
    element.innerText = dataSet.value

    // increase the value of index gradually
    index++

    hackerEffect()
    timeOut = setTimeout(script,3000)
},1000)

From Now on we have two options to go with

  1. To stop after generating all the words in the array

  2. To loop over the array for forever.

For the first option, add this condition at the end of timeOut

...
    if (index >= words.length) {
        clearTimeout(timeOut)
    }

This will check if the value of index is greater than or equal to the length of given word's array, then it will clear out the setTimeout method, thus stopping the hackerEfect.

For the second option, add this condition at the end of timeOut.

...
    if (index >= words.length) {
        index = 0;
    }

Here, all 3 of our objectives are achieved.


Remember ?, we have to play with different values of timings, Let's just fix that issue.

For that, let's create iteratorController letterSpeed, defaultDelay & speed variables.

Create letterSpeed variable before hackerEffect function, as it will hold the delay value of setInterval. and replace the hard-coded value of interval delay to letterSpeed

...
let letterSpeed = 100 //new
let iteratorControler = 1/2  //new
// main function
function hackerEffect() {
    clearInterval(interval)

    let iterator = 0
    let arbitraryValues = randomArray(dataSet.value.length);

    interval = setInterval(() => {
        element.innerText = element.innerText
            .split("")
            .map((letter, index) => {
                if (iterator >= arbitraryValues[index]) {
                    return dataSet.value[index];
                }
                return chars[randomNumber(chars.length)];
            })
            .join("");
            if (iterator === dataSet.value.length) {
                clearInterval(interval)
            }
           iterator += 1/2
//        }, 100); //old
          }, letterSpeed); //new
}
...

Create iteratorController for controlling the behaviour of iterator. after letterSpeed. Replace the hard-coded value of 1/2 in the interval.

// main function
function hackerEffect() {
    clearInterval(interval)

    let iterator = 0
    let arbitraryValues = randomArray(dataSet.value.length);

    interval = setInterval(() => {
        element.innerText = element.innerText
            .split("")
            .map((letter, index) => {
                if (iterator >= arbitraryValues[index]) {
                    return dataSet.value[index];
                }
                return chars[randomNumber(chars.length)];
            })
            .join("");
            if (iterator === dataSet.value.length) {
                clearInterval(interval)
            }
       //   iterator += 1/2 // old
            iterator += iteratorController // new  
        }, letterSpeed); 
}

Since the defaultDelay is just hard-coded value we do not have to calculate it every time, thus it should be outside the timeOut. But the speed is calculated every time a word changes and is dynamic, thus it should be inside timeOut.

...
let defaultdelay = 2000
...
let timeOut = setTimeout...

    let speed = 0;

The defaultDelay will hold our desired delay time and will mainly be used in addition with speed.

The value of speed will be calculated by the below formula: (I made it up πŸ˜Άβ€πŸŒ«οΈ)

speed = letterSpeed words.length dataSet.value.length + defaultDelay + ((1 / iteratorController) dataSet.value.length letterSpeed)

word.length & dataSet.value.length are the length of array and the length of text in dataSet respectively.

The speed problem is fixed.

Now, no matter How big the word/sentence is, we will at least have a delay of 2 seconds before the word/sentence is replaced with the next word/sentence.

4. Wrapping up

Since we the array does not include our initial word, (here "xivLogBook") we can add the initial word in the array using array.push() method

// array of words
let words = ["Say Hello to-", "JavaScript","Decryption Animation", "made by"]

// push the very first word in the array
words.push(dataSet.value)

Hacker-Effect is a cool and catchy name, but I think the more logical name is Decryption-Animation. since this effect is mimicking the decryption-animation, also hacker-effect is more synonymous with the matrix effect.

Result

GitHub Pages


Credits:

  1. Hyperplexed for his amazing tutorial.

  2. JavaScript Info for setTimeout vs setInterval .

  3. KPR | Story, for inspiration.

Β