Zebeth Media Solutions

Tutorials

3D Typing Effects with Three.js

From our sponsor: Get suggestions for improving your content, targeting, and marketing automations to help you increase revenue. In this tutorial we’ll explore various animated WebGL text typing effects. We will mostly be using Three.js but not the whole tutorial relies on the specific features of this library. But who doesn’t love Three.js though ❤️ This tutorial is aimed at developers who are familiar with the basic concepts of WebGL. The main idea is to create a JavaScript template that takes a keyboard input and draws the text on the screen in some fancy way. The effects we will build today are all about composing a text shape with a big number of repeating objects. We will cover the following steps: Sampling text on Canvas (generating 2D coordinates)Setting up the scene and placing the Canvas elementGenerating particles in 3D spaceTurning particles to an instanced meshReplacing a static string with some user inputBasic animationTyping-related animationGenerating the visuals: clouds, bubbles, flowers, eyeballs Text sampling In the following we will fill a text shape with some particles. First, let’s think about what a 3D text shape is. In general, a text mesh is nothing but a 2D shape being extruded. So we don’t need to sample the 3rd coordinate – we can just use X/Y coordinates with Z being randomly generated within the text depth (although we’re not about to use the Z coordinate much today). One of the ways to generate 2D coordinates inside the shape is with Canvas sampling. So let’s create a <canvas> element, apply some font-related styles to it and make sure the size of <canvas> is big enough for the text to fit (extra space is okay). // Settings const fontName = ‘Verdana’; const textureFontSize = 100; // String to show let string = ‘Some text’ + ‘\n’ + ‘to sample’ + ‘\n’ + ‘with Canvas’; // Create canvas to sample the text const textCanvas = document.createElement(‘canvas’); const textCtx = textCanvas.getContext(‘2d’); document.body.appendChild(textCanvas); // ————————————————————— sampleCoordinates(); // ————————————————————— function sampleCoordinates() { // Parse text const lines = string.split(`\n`); const linesMaxLength = […lines].sort((a, b) => b.length – a.length)[0].length; const wTexture = textureFontSize * .7 * linesMaxLength; const hTexture = lines.length * textureFontSize; // … } With the Canvas API you can set all the font styling pretty much like in CSS. Custom fonts can be used as well, but I’m using good old Verdana today. Once the style is set, we draw the text (or any other graphics!) on the <canvas>… function sampleCoordinates() { // Parse text // … // Draw text const linesNumber = lines.length; textCanvas.width = wTexture; textCanvas.height = hTexture; textCtx.font = ‘100 ‘ + textureFontSize + ‘px ‘ + fontName; textCtx.fillStyle = ‘#2a9d8f’; textCtx.clearRect(0, 0, textCanvas.width, textCanvas.height); for (let i = 0; i < linesNumber; i++) { textCtx.fillText(lines[i], 0, (i + .8) * hTexture / linesNumber); } // … } … to be able to get imageData from it. The ImageData object contains a one-dimensional array with RGBA data for every pixel. Knowing the size of the canvas, we can go through the array and check if the given X/Y coordinate matches the color of text or the color of the background. Since our canvas doesn’t have anything but colored text on the unset (transparent black) background, we can check any of the four RGBA bytes with against a condition as simple as “bigger than zero”. function sampleCoordinates() { // Parse text // … // Draw text // … // Sample coordinates textureCoordinates = []; const samplingStep = 4; if (wTexture > 0) { const imageData = textCtx.getImageData(0, 0, textCanvas.width, textCanvas.height); for (let i = 0; i < textCanvas.height; i += samplingStep) { for (let j = 0; j < textCanvas.width; j += samplingStep) { // Checking if R-channel is not zero since the background RGBA is (0,0,0,0) if (imageData.data[(j + i * textCanvas.width) * 4] > 0) { textureCoordinates.push({ x: j, y: i }) } } } } } There’re lots of things you can do with the sampling function: change the sampling step, add some randomness, apply an outline stroke to the text, and more. Below we’ll keep using only the simplest sampling. To check the result we can add a second <canvas> and draw the dot for each of sampled textureCoordinates. It works 🙂 The Three.js scene Let’s set up a basic Three.js scene and place a Plane object on it. We can use the text sampling Canvas from the previous step as a color map for the Plane. Generating the particles We can generate 3D coordinates with the very same sampling function. X/Y are gathered from the Canvas and for the Z coordinate we can take a random number. The easiest way to visualize this set of coordinates would be a particle system known as THREE.Points. function createParticles() { const geometry = new THREE.BufferGeometry(); const material = new THREE.PointsMaterial({ color: 0xff0000, size: 2 }); const vertices = []; for (let i = 0; i < textureCoordinates.length; i ++) { vertices.push(textureCoordinates[i].x, textureCoordinates[i].y, 5 * Math.random()); } geometry.setAttribute(‘position’, new THREE.Float32BufferAttribute(vertices, 3)); const particles = new THREE.Points(geometry, material); scene.add(particles); } Somehow it works ¯\_(ツ)_/¯ Obviously, we need to flip the Y coordinate for each particle and center the whole text. To do both, we need to know the bounding box of our text. There are various ways to measure the box using the canvas API or Three.js functions. But as a temporary solution, we just take max X and Y coordinates as width and height of the text. function refreshText() { sampleCoordinates(); // Gather with and height of the bounding box const maxX = textureCoordinates.map(v => v.x).sort((a, b) => (b – a))[0]; const maxY = textureCoordinates.map(v => v.y).sort((a, b) => (b – a))[0]; stringBox.wScene = maxX; stringBox.hScene = maxY; createParticles(); } For each point, the Y coordinate becomes boxTotalHeight – Y. Shifting the whole particles system by half-width and half-height of the box solves the centering issue. function createParticles() { // … for (let i = 0; i < textureCoordinates.length; i ++) { //

Subscribe to Zebeth Media Solutions

You may contact us by filling in this form any time you need professional support or have any questions. You can also fill in the form to leave your comments or feedback.

We respect your privacy.
business and solar energy