How to generate a random distribution to make characters (in a word/phrase) appear more naturally

160 Views Asked by At

This is the effect I am trying to achieve: http://codepen.io/anon/pen/ENzQem

I am trying to generate an effect where the letters of a string get revealed gradually and randomly. See the codepen link above for a demonstration of this.

However, I'm finding it difficult to show each character naturally if I just simply randomly generate numbers for each delay separately.

If I simply do a Math.random() to generate a number independently for each character, sometimes adjacent letters will have similar delay numbers, and as such the effect will look chunky, with two letters side-by-side appearing at the same rate.

This is the naive solution with separate random number generators:

  renderSpans(text) {
    const textArray = text.split('');
    return textArray.map((letter, index) => {
      const transitionTime = 2000;
      const delay = parseInt(Math.random() * transitionTime, 10);
      const styles = {
        opacity: this.props.show ? '1' : '0',
        transition: `opacity ${transitionTime}ms`,
        transitionDelay: `${delay}ms`,
      };
      return <span style={styles}>{letter}</span>;
    });
  }

I need an algorithm to generate an array of numbers that I can use as the delay for each of the characters, regardless of the length of the input string.

My first thought is to use a sinusoidal wave of some sort, with a bunch of randomness put in, but I'm not sure about the specifics on this. I am sure there's a much more well-accepted way to generate natural-looking noise in mathematics.

Can someone point me to some well-known algorithms for my use case? Maybe something to do with Perlin noise or the like?

Thanks in advance.

1

There are 1 best solutions below

0
On BEST ANSWER

It turns out I was overthinking the problem.

I ended up creating the following function:

const getRandoms = (length, threshold) => {
  const tooClose = (a, b) => Math.abs(a - b) < threshold;

  const result = [];
  let random;

  for (let i = 0; i < length; i += 1) {
    random = Math.random();
    if (i !== 0) {
      const prev = result[i - 1];
      while (tooClose(random, prev)) {
        random = Math.random();
      }
    }
    result.push(random);
  }
  return result;
};

I originally wrote a version of this function that uses reduce rather than a for-loop, but ultimately decided that a for-loop would be clearer and simpler.

How it works

This function procedurally builds up an array of random numbers, given the required length and a "closeness" threshold. Each of the resulting random numbers will have a difference greater than the threshold when compared with the previous number in the array.

  1. For the first item in the array, we simply generate a random number and push it in.

  2. For each of the subsequent items in the array, we:

    • Generate a new random number,
    • Compare this number to the previous number in the array,
    • If they are too close (i.e. difference < a threshold value), then generate a new random number and compare again,
    • If they are not too close, then push it into the results array and continue.

I have found that a threshold of 0.2 seem to work pretty well for my use case.