'Enable Moving Content On Click for Dark Background

I am trying to replicate the Matrix green code. I want to make the content dark when loaded and then add an event listener to say on click enable the falling green symbols.

How can you add an event listener to enable the moving content while loading it a dark (solid black) screen to begin with? - click and move your mouse around enables falling symbols - interactive experience.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Matrix</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    <canvas id="canvas"></canvas>
    <script src="matrix.js"></script>
</body>
</html>
//@ts-check
const symbols = [
  "日",
  "ハ",
  "ミ",
  "ヒ",
  "ー",
  "ウ",
  "シ",
  "ナ",
  "モ",
  "ニ",
  "サ",
  "ワ",
  "ツ",
  "オ",
  "リ",
  "ア",
  "ホ",
  "テ",
  "マ",
  "ケ",
  "メ",
  "エ",
  "カ",
  "キ",
  "ム",
  "ユ",
  "ラ",
  "セ",
  "ネ",
  "ス",
  "タ",
  "ヌ",
  "ヘ",
  "0",
  "1",
  "2",
  "3",
  "4",
  "5",
  "7",
  "8",
  "9",
  "T",
  "H",
  "E",
  "M",
  "A",
  "T",
  "R",
  "I",
  "X",
  ":",
  "・",
  ".",
  "=",
  "*",
  "+",
  "-",
  "<",
  ">",
  "¦",
  "|",
  "ク",
  "ç",
  "リ",
  "Ɛ",
]

/**
 * @typedef {Object} IColor
 * @property {number} red
 * @property {number} green
 * @property {number} blue
 * @property {number=} alpha
 */

/**
 * @typedef {Object} IConfiguration
 * @property {number} maxSymbolCount
 * @property {number} symbolAlphaFadeRate
 * @property {number} symbolFontSize
 * @property {string} symbolFontFamily
 * @property {IColor} symbolColorForeground
 * @property {IColor} symbolColorFade
 * @property {IColor} canvasBackgroundColor
 * @property {number} frameRatePerSecond
 */

/**
 * @typedef {Object} ISymbolCoordinate
 * @property {string} symbol
 * @property {number} x
 * @property {number} y
 */

const getCanvasHeight = () => {
  return window.innerHeight
}

const getCanvasWidth = () => {
  return window.innerWidth
}

const getRandomSymbol = () =>
  symbols[Math.floor(Math.random() * (symbols.length - 1))]

/**
 * @param {ISymbolCoordinate} coordinate
 * @param {number} symbolFontSize
 * @return {ISymbolCoordinate}
 */
const calcFallSymbolCoordinate = ({ x, y }, symbolFontSize) => {
  const fallDistance =
    (Math.random() * symbolFontSize * 3) / 4 + (symbolFontSize * 3) / 4
  const newY = y + fallDistance

  return newY > getCanvasHeight()
    ? initializeSymbolCoordinate(symbolFontSize, 8)
    : {
        symbol: getRandomSymbol(),
        x,
        y: newY,
      }
}

/**
 * @param {ISymbolCoordinate[]} symbolsPool
 * @param {number} symbolFontSize
 */
const calcNextFrameSymbolsFall = (symbolsPool, symbolFontSize) =>
  symbolsPool.map((coordinate) =>
    calcFallSymbolCoordinate(coordinate, symbolFontSize)
  )

/** @param {IColor} color*/
const formatToRbgString = ({ red, green, blue, alpha }) =>
  `rgba(${red}, ${green}, ${blue}, ${alpha || 1})`

/** @param {number} symbolFontSize*/
/** @param {string} symbolFontFamily*/
const formatToFontString = (symbolFontSize, symbolFontFamily) =>
  `${symbolFontSize}px ${symbolFontFamily}`

/**
 * @param {number} symbolFontSize
 * @return {ISymbolCoordinate}
 */
const initializeSymbolCoordinate = (symbolFontSize, offset = 1) => {
  return {
    symbol: getRandomSymbol(),
    x:
      Math.floor((Math.random() * getCanvasWidth()) / symbolFontSize) *
      symbolFontSize,
    y: (Math.random() * getCanvasHeight()) / offset - 50,
  }
}

/** @param {number} ms */
const wait = (ms) => new Promise((res) => setTimeout(res, ms))

/**
 * @param {HTMLCanvasElement} canvas
 */
const watchScreenForCanvasDimensions = (canvas) => {
  const resizeCanvas = () => {
    canvas.width = getCanvasWidth()
    canvas.height = getCanvasHeight()
  }

  resizeCanvas() // On init
  window.addEventListener("resize", resizeCanvas) // On screen resize
}

/**
 * @param {IConfiguration} config
 * @returns {{
 *   renderToCanvas: (coordinates: ISymbolCoordinate[], symbolColor: IColor) => void
 * }}
 */
const configureCanvasFnFactory = (config) => {
  // Canvas config
  const {
    symbolFontSize,
    symbolFontFamily,
    canvasBackgroundColor,
    symbolAlphaFadeRate,
  } = config
  const canvas = /** @type {HTMLCanvasElement} */ (
    document.getElementById("canvas")
  )
  const canvasContext = canvas.getContext("2d")
  watchScreenForCanvasDimensions(canvas)

  // Init Background
  canvasContext.fillStyle = formatToRbgString({
    ...canvasBackgroundColor,
  })
  canvasContext.fillRect(0, 0, getCanvasWidth(), getCanvasHeight())

  // Returned function to render canvas
  return {
    renderToCanvas: (coordinates, symbolColor) => {
      // Draw Background
      canvasContext.fillStyle = formatToRbgString({
        ...canvasBackgroundColor,
        alpha: symbolAlphaFadeRate,
      })
      canvasContext.fillRect(0, 0, getCanvasWidth(), getCanvasHeight())

      // Draw Symbols
      canvasContext.font = formatToFontString(symbolFontSize, symbolFontFamily)
      canvasContext.fillStyle = formatToRbgString(symbolColor)
      coordinates.forEach(({ symbol, x, y }) => {
        canvasContext.fillText(symbol, x, y)
      })
    },
  }
}

/** @param {IConfiguration} config */
const runMatrix = (config) => {
  const {
    maxSymbolCount,
    symbolFontSize,
    frameRatePerSecond,
    symbolColorFade,
    symbolColorForeground,
  } = config
  const { renderToCanvas } = configureCanvasFnFactory(config)

  let symbolsPool = Array.from(Array(maxSymbolCount), () =>
    initializeSymbolCoordinate(symbolFontSize)
  )

  const stepAnimation = async (frameNumber = 0) => {
    if (frameNumber % 2 === 0) {
      symbolsPool = calcNextFrameSymbolsFall(symbolsPool, symbolFontSize)
      renderToCanvas(symbolsPool, symbolColorFade)
    } else {
      renderToCanvas(symbolsPool, symbolColorForeground)
    }

    // Render next frame
    window.requestAnimationFrame(async () => {
      await wait(1000 / frameRatePerSecond)
      stepAnimation(frameNumber + 1)
    })
  }
  stepAnimation() // Begin animation recursion
}

const main = () => {
  const config = /** @type {IConfiguration} */ ({
    canvasBackgroundColor: { red: 0, green: 0, blue: 0 },
    frameRatePerSecond: 30,
    maxSymbolCount: 60,
    symbolAlphaFadeRate: 0.05,
    symbolColorFade: { red: 255, green: 255, blue: 255 },
    symbolColorForeground: { red: 0, green: 255, blue: 0 },
    symbolFontSize: 16,
  })
  runMatrix(config)
}
main()
body {
    margin: 0 auto;
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source