# Code generative animation -- Ice **Published by:** [Ricy137](https://paragraph.com/@ricy137/) **Published on:** 2024-03-15 **Categories:** frontend, animation, generative-art **URL:** https://paragraph.com/@ricy137/code-generative-animation-ice ## Content When I first landed on AntFu’s website AntFu, I was blown away by the incredible background animation⬇. Intrigued as I was, I initially shied away from attempting it, thinking it might be too complex for me.plum by antfuUntil recently, I went to Denver and my host, an artist, told me it's generative-art and not hard once learned the underlying algorithm. My fear is reduced after knowing what the animation is, and in fact, AntFu has also shared lots of knowledge of such animations in his stream or his project. Inspired by AntFu's work, I decided to experiment with creating a similar animation, focusing on something I've always felt inspired by—ice. (You can follow my entire learning and experimentation journey here)iceSo, how do you code a generative art animation?Generally speaking, you need three key components:A canvas to illustrate the animation on.An algorithm for running the generation process.A method to animate the generation process.Now, let's dive into coding the Ice animation. A word of caution: this article won't provide a step-by-step coding guide. While I'll outline code snippets, I'll skip some trivial steps. You can find all the codes here.The algorithm closely resembles another project by AntFu, which is fantastic work! I highly recommend visiting his website to explore more amazing projects and gain further inspiration.Use canvas with reactIt’s a topic that has been extensively covered in various articles, so I won't delve into it here. You can refer to this article to learn how to integrate canvas with React. Additionally, I've developed a component that you may find useful.Next, let's talk about the algorithmWe'll employ the ImageData object and putImageData method to manipulate pixels on the canvas. More details can be found here. The Ice animation algorithm primarily consists of two parts: color change and pixel manipulation. Color change: Upon closer inspection of the animation, you'll notice a gradual shift in color from white to deep blue. To achieve this effect, I've predefined six shades of blue and one white, resulting in a palette of seven distinct colors: ['#ffffff', '#caf0f8', '#ade8f4', '#90e0ef', '#48cae4', '#00b4d8', '#0096c7']. These colors were carefully chosen from Coolors.co. However, it would be too boring if the color just transitioned bluer and bluer as the color predefined. To add more dynamism and depth, we introduce randomness into the color transition process. Let's convert these colors from Hex to RGB format (or utilize RGB format from the outset) for further manipulation. //@/utils/colors.ts export type ColorVector = [number, number, number]; export function hexToRgb(hex: string): ColorVector { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!; return [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16), ]; } //@/utils/canvas/ice.ts const pattele = [ '#ffffff', '#caf0f8', '#ade8f4', '#90e0ef', '#48cae4', '#00b4d8', '#0096c7', ].map(hexToRgb); As previously mentioned, we'll be pixel-manipulating the canvas. Thus, we'll assign a value 'n' ∈ [0,1] to represent the degree of blueness for each pixel. The closer the 'n' value is to 1,the deeper the shade of blue. We'll define the colorInterpretation helper function as shown below. Then we turn the discrete color picker function into a linear one, providing more options for the color transition process, and thereby offering more flexibility in the color transition process. This function also introduces randomness into the process, adding an element of unpredictability. /** * @param vectors * @param num 0~1 */ export function colorInterpration(vectors: ColorVector[], n: number) { if (n >= 1) return vectors[vectors.length - 1]; const normalized = clamp(n, 0, 1) * (vectors.length - 1); const integer = Math.trunc(normalized); const frac = normalized - integer; const nfrac = 1 - frac; const [a1, a2, a3] = vectors[integer]; const [b1, b2, b3] = vectors[integer + 1]; return [ a1 * nfrac + b1 * frac, a2 * nfrac + b2 * frac, a3 * nfrac + b3 * frac, ]; } Pixel manipulation: First, we'll create a 2D array, 'iceNodes', to represent the degree of blueness for each pixel. We'll initialize this array as follows: let iceNodes: number[][] = []; iceNodes = new Array(width) .fill(0) .map((_, i) => i) .map((i) => new Array(height).fill(0)); The algorithm for generating the Ice animation entails starting from an active point, intensifying its blueness, and adding neighboring points to the active points group. This process continues recursively until we reach the maximum iteration limit. export type Vector = [number, number]; // x, y position of the point class Ice { constructor(public activePoints: Vector[], public iteractions = 5) {} next() { if (!this.iteractions) return; this.iteractions -= 1; const newPoints: Vector[] = []; this.activePoints.forEach((point) => { const [x, y] = point; iceNodes[x][y] += randomWithRange(0.1, 0); const points: Vector[] = [ [x, y], [x, y + 1], [x + 1, y], [x, y - 1], [x - 1, y], [x + 1, y + 1], [x + 1, y - 1], [x - 1, y + 1], [x - 1, y - 1], ]; newPoints.push( ...points .filter((v) => !newPoints.some((n) => n[0] === v[0] && n[1] === v[1])) .filter((v) => inbound(v)) .filter(([x, y]) => { if (iceNodes[x][y] === 0) return true; if (iceNodes[x][y] >= 1) return false; if (iceNodes[x][y] > 0.8) return randomWithRange() > 0.5; else return randomWithRange() > 0.2; }) ); }); this.activePoints = sampleSize(newPoints, 200); } }Updating the Canvas and Creating the AnimationAfter each iteration of Ice, where pixels are updated, we need to then go ahead and update the canvas to reflect these changes. Now, onto the animation itself. First, after the next() method of Ice class, some pixels' n are updated, but our canvas isn’t in the know. So we need to update ImageData to ensure the blueness of each pixel in our canvas is updated.function updateCanvas() { for (let x = 0; x < width; x++) { for (let y = 0; y < height; y++) { const iceNode = iceNodes[x][y]; const [r, g, b] = colorInterpration(pattele, iceNode); const pixelindex = (y * width + x) * 4; data.data[pixelindex] = r; data.data[pixelindex + 1] = g; data.data[pixelindex + 2] = b; data.data[pixelindex + 3] = 255; } } ctx.putImageData(data, 0, 0); }The animation comes to life using the requestAnimationFrame function, similar to setTimeout but optimized for smooth animations(read difference here). Control over the animation frequency is achieved using the frameCount variable, while tick and maxTicks dictate the maximum number of frames.const frame = () => { tick++; for (let i = 0; i < iterations; i++) { iceField.forEach((i) => { i.next(); i.next(); i.next(); i.next(); }); } updateCanvas(); if (tick >= maxTicks) throw new Error('done'); }; const start = () => { iceField = [ new Ice( [ [0, Math.trunc(randomWithRange(400))], [Math.trunc(randomWithRange(400)), 0], [399, Math.trunc(randomWithRange(400))], [Math.trunc(randomWithRange(400)), 399], ], maxTicks * iterations ), new Ice(randomVectors(40), (maxTicks * iterations) / 2), new Ice(randomVectors(3), (maxTicks * iterations) / 1.5), ]; let frameCount = 0; const startFrame = () => { try { frameCount++; requestAnimationFrame(() => { frame(); startFrame(); }); } catch (e) {} }; startFrame(); };And there you have it—a captivating Ice animation!Thanks for reading, I hope you enjoyed this mini-tutorial and feel inspired to adapt the code provided for your own projects. And the final codes can be found in my gitHub. Don't hesitate to reach out to me on GItHub @Ricy137, Lens @cuckooir or X/twitter @MaryChao to chat further <3 ~ ## Publication Information - [Ricy137](https://paragraph.com/@ricy137/): Publication homepage - [All Posts](https://paragraph.com/@ricy137/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@ricy137): Subscribe to updates ## Optional - [Collect as NFT](https://paragraph.com/@ricy137/code-generative-animation-ice): Support the author by collecting this post - [View Collectors](https://paragraph.com/@ricy137/code-generative-animation-ice/collectors): See who has collected this post