Archiwum kategorii: CSS

Techniques for Rendering Text with WebGL

Post pobrano z: Techniques for Rendering Text with WebGL

As is the rule in WebGL, anything that seems like it should be simple is actually quite complicated. Drawing lines, debugging shaders, text rendering… they are all damn hard to do well in WebGL.

Isn’t that weird? WebGL doesn’t have a built-in function for rendering text. Although text seems like the most basic of functionalities. When it comes down to actually rendering it, things get complicated. How do you account for the immense amount of glyphs for every language? How do you work with fixed-width, or proportional-width fonts? What do you do when text needs to be rendered top-to-bottom, left-to-right, or right-to-left? Mathematical equations, diagrams, sheet music?

Suddenly it starts to make sense why text rendering has no place in a low-level graphics API like WebGL. Text rendering is a complex problem with a lot of nuances. If we want to render text, we need to get creative. Fortunately, a lot of smart folks already came up with a wide range of techniques for all our WebGL text needs.

We’ll learn at some of those techniques in this article, including how to generate the assets they need and how to use them with ThreeJS, a JavaScript 3D library that includes a WebGL renderer. As a bonus, each technique is going to have a demo showcasing use cases.

Table of Contents


A quick note on text outside of WebGL

Although this article is all about text inside WebGL, the first thing you should consider is whether you can get away with using HMTL text or canvas overlayed on top of your WebGL canvas. The text can’t be occluded with the 3D geometry as an overlay, but you can get styling and accessibility out of the box. That’s all you need in a lot of cases.

Font geometries

One of the common ways to render text is to build the glyphs with a series of triangles, much like a regular model. After all, rendering points, lines and triangles are a strength of WebGL.

When creating a string, each glyph is made by reading the triangles from a font file of triangulated points. From there, you could extrude the glyph to make it 3D, or scale the glyphs via matrix operations.

Regular font representation (left) and font geometry (right)

Font geometry works best for a small amount of text. That’s because each glyph contains many triangles, which causes drawing to become problematic.

Rendering this exact paragraph you are reading right now with font geometry creates 185,084 triangles and 555,252 vertices. This is just 259 letters. Write the whole article using a font geometry and your computer fan might as well become an airplane turbine.

Although the number of triangles varies by the precision of the triangulation and the typeface in use, rendering lots of text will probably always be a bottleneck when working with font geometry.

How to create a font geometry from a font file

If it were as easy as choosing the font you want and rendering the text. I wouldn’t be writing this article. Regular font formats define glyphs with Bezier curves. On the flip side, drawing those in WebGL is extremely expensive on the CPU and is also complicated to do. If we want to render text, we need to create triangles (triangulation) out of Bezier curves.

I’ve found a few triangulation methods, but by no means are any of them perfect and they may not work with every font. But at least they’ll get you started for triangulating your own typefaces.

Method 1: Automatic and easy

If you are using ThreeJS, you pass your typeface through FaceType.js to read the parametric curves from your font file and put them into a .json file. The font geometry features in ThreeJS take care of triangulating the points for you:

const geometry = new THREE.FontGeometry("Hello There", {font: font, size: 80})

Alternatively, if you are not using ThreeJS and don’t need to have real-time triangulation. You could save yourself the pain of a manual process by using ThreeJS to triangulate the font for you. Then you can extract the vertices and indices from the geometry, and load them in your WebGL application of choice.

Method 2: Manual and painful

The manual option for triangulating a font file is extremely complicated and convoluted, at least initially. It would require a whole article just to explain it in detail. That said, we’ll quickly go over the steps of a basic implementation I grabbed from StackOverflow.

See the Pen
Triangulating Fonts
by Daniel Velasquez (@Anemolo)
on CodePen.

The implementation basically breaks down like this:

  1. Add OpenType.js and Earcut.js to your project.
  2. Get Bezier curves from your .tff font file using OpenType.js.
  3. Convert Bezier curves into closed shapes and sort them by descending area.
  4. Determine the indices for the holes by figuring out which shapes are inside other shapes.
  5. Send all of the points to Earcut with the hole indices as a second parameter.
  6. Use Earcut’s result as the indices for your geometry.
  7. Breath out.

Yeah, it’s a lot. And this implementation may not work for all typefaces. It’ll get you started nonetheless.

Using text geometries in ThreeJS

Thankfully, ThreeJS supports text geometries out of the box. Give it a .json of your favorite font’s Bezier curves and ThreeJS takes care of triangulating the vertices for you in runtime.

var loader = new THREE.FontLoader();
var font;
var text = "Hello World"
var loader = new THREE.FontLoader();
loader.load('fonts/helvetiker_regular.typeface.json', function (helvetiker) {
  font = helvetiker;
  var geometry = new THREE.TextGeometry(text, {
    font: font,
    size: 80,
    height: 5,
  });
}

Advantages

  • It’s easily extruded to create 3D strings.
  • Scaling is made easier with matrix operations.
  • It provides great quality depending on the amount of triangles used.

Disadvantages

  • This doesn’t scale well with large amounts of text due to the high triangle count. Since each character is defined by a lot of triangles, even rendering something as brief as „Hello World” results in 7,396 triangles and 22,188 vertices.
  • This doesn’t lend itself to common text effects.
  • Anti-aliasing depends on your post-processing aliasing or your browser default.
  • Scaling things too big might show the triangles.

Demo: Fading Letters

In the following demo, I took advantage of how easy it is to create 3D text using font geometries. Inside a vertex shader, the extrusion is increased and decreased as time goes on. Pair that with fog and standard material and you get these ghostly letters coming in and out of the void.

Notice how with a low amount of letters the amount of triangles is already in the tens of thousands!

Text (and canvas) textures

Making text textures is probably the simplest and oldest way to draw text in WebGL. Open up Photoshop or some other raster graphics editor, draw an image with some text on it, then render these textures onto a quad and you are done!

Alternatively, you could use the canvas to create the textures on demand at runtime. You’re able to render the canvas as a texture onto a quad as well.

Aside for being the least complicated technique of the bunch. Text textures and canvas textures have the benefit of only needed one quad per texture, or given piece of text. If you really wanted to, you could write the entire British Encyclopedia on a single texture. That way, you only have to render a single quad, six vertices and two faces. Of course, you would do it in a scale, but the idea still remains: You can batch multiple glyphs into same quad. Both text and canvas textures experience have issues with scaling, particularly when working with lots of text.

For text textures, the user has to download all the textures that make up the text, then keep them in memory. For canvas textures, the user doesn’t have to download anything — but now the user’s computer has to do all the rasterizing at runtime, and you need to keep track of where every word is located in the canvas. Plus, updating a big canvas can be really slow.

How to create and use a text texture

Text textures don’t have anything fancy going on for them. Open up your favorite raster graphics editor, draw some text on the canvas and export it as an image. Then you can load it as a texture, and map it on a plane:

// Load texture
let texture = ;
const geometry = new THREE.PlaneBufferGeometry();
const material new THREE.MeshBasicMaterial({map: texture});
this.scene.add(new Mesh(geometry,material));

If your WebGL app has a lot of text, downloading a huge sprite sheet of text might not be ideal, especially for users on slow connections. To avoid the download time, you can rasterize things on demand using an offscreen canvas then sample that canvas as a texture.

Let’s trade download time for performance since rasterizing lots of text takes more than a moment.

function createTextCanvas(string, parameters = {}){
    
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    
    // Prepare the font to be able to measure
    let fontSize = parameters.fontSize || 56;
    ctx.font = `${fontSize}px monospace`;
    
    const textMetrics = ctx.measureText(text);
    
    let width = textMetrics.width;
    let height = fontSize;
    
    // Resize canvas to match text size 
    canvas.width = width;
    canvas.height = height;
    canvas.style.width = width + "px";
    canvas.style.height = height + "px";
    
    // Re-apply font since canvas is resized.
    ctx.font = `${fontSize}px monospace`;
    ctx.textAlign = parameters.align || "center" ;
    ctx.textBaseline = parameters.baseline || "middle";
    
    // Make the canvas transparent for simplicity
    ctx.fillStyle = "transparent";
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    
    ctx.fillStyle = parameters.color || "white";
    ctx.fillText(text, width / 2, height / 2);
    
    return canvas;
}

let texture = new THREE.Texture(createTextCanvas("This is text"));

Now you can use the texture on a plane, like the previous snippet. Or you could create a sprite instead.

As an alternative, you could use more efficient libraries to create texture or sprites, like three-text2d or three-spritetext. And if you want text multi-line text, you should check out this amazing tutorial.

Advantages

  • This provides great 1-to-1 quality with static text.
  • There’s a low vertex/face count. Each string can use as little as six vertices and two faces.
  • It’s easy to implement texture on a quad.
  • It’s fairly trivial to add effects, like borders and glows, using canvas or a graphics editor.
  • Canvas makes it easy to create multi-line text.

Disadvantages

  • Looks blurry if scaled, rotated or transformed after rasterizing.
  • On-non retina, text looks crunchy.
  • You have to rasterize all the strings used. A lot of strings means a lot of data to download.
  • On-demand rasterizing with canvas can be slow if you keep constantly updating the canvas.

Demo: Canvas texture

Canvas textures work well with a limited amount of text that doesn’t change often. So I built a simple wall of text with the quads re-using the same texture.

Bitmap fonts

Both font geometries and text textures experience the same problems handling lots of text. Having one million vertices per piece of text is super inefficient, and creating one texture per piece of text doesn’t really scale.

Bitmap fonts solve this issue by rasterizing all unique glyphs into a single texture, called a texture atlas. This means you can assemble any given string at runtime by creating a quad for each glyph and sampling the section of the texture atlas.

This means users only have to download and use a single texture for all of the text. It also means you only need to render as little as one quad per glyph:

A visual of bitmap font sampling

Rendering this whole article would be approximately 117,272 vertices and 58,636 triangles. That’s 3.1 times more efficient compared to a font geometry rendering just a single paragraph. That a huge improvement!

Because bitmap fonts rasterize the glyph into a texture, they suffer from the same problem as regular images. Zoom in or scale and you start seeing a pixelated and blurry mess. If you want text at a different size, you should send a secondary bitmap with the glyphs on that specific size. Or you could use a Signed Distance Field (SDF) which we’ll cover in the next section.

How to create bitmap fonts

There are a lot of tools to generate bitmaps. Here are some of the more relevant options out there:

  • Angelcode’s bmfont – This is by the creators of the bitmap format.
  • Hiero – This is a Java open-source tool. It’s very similar to Anglecode’s bmfont, but it allows you to add text effects.
  • Glyphs Designer – This is a paid MacOS app.
  • ShoeBox – This is an tool for dealing with sprites, including bitmap fonts.

We’ll use Anglecode’s bmfont for our example because I think it’s the easiest one to get started. At the bottom of this section, you can find other tools if you think it lacks the functionality you are looking for.

When you open the app, you’ll see a screen full of letters that you can select to use.The nice thing about this is that you are able to grab just the glyphs you need instead of sending Greek symbols.

The app’s sidebar allows you to choose and select groups of glyphs.

The BmFont application

Ready to export? Go to OptionsSave bitmap as. And done!

But we’re getting a little ahead of ourselves. Before exporting, there are a few important settings you should check.

Export and Font Option settings
  • Font settings: This let you choose the font and size you want to use. The most important item here is „Match char height.” By default, the app’s „size” option uses pixels instead of points. You’ll see a substantial difference between your graphics editor’s font size and the font size that is generated. Select the „Match char height” options if you want your glyphs to make sense.
  • Export settings: For the export, make sure the texture size is a power of two (e.g. 16×16, 32×32, 64×64, etc.). Then you are able to take advantage of „Linear Mipmap linear” filtering, if needed.

At the bottom of the settings, you’ll see the „file format” section. Choosing either option here is fine as long as you can read the file and create the glyphs.

If you are looking for the smallest file size. I ran a ultra non-scientific test where I created a bitmap of all lowecase and uppercase Latin characters and compared each option. For Font Descriptors, the most efficient format is Binary.

Font Descriptor Format File Size
Binary 3 KB
Raw Text 11 KB
XML 12 KB
Texture Format File Size
PNG 7 KB
Targa 64 KB
DirectDraw Surface 65 KB

PNG is the smallest file size for Text Texture.

Of course, it’s a little more complicated than just file sizes. To get a better idea of which option to use, you should look into parsing time and run-time performance. If you would like to know the pros and cons of each formats, check out this discussion.

How to use bitmap fonts

Creating bitmap font geometry is a bit more involved than just using a texture because we have to construct the string ourselves. Each glyph has its own height and width, and samples a different section of the texture. We have to create a quad for each glyph on our string so we can give them the correct UVs to sample it’s glyph.

You can use three-bmfont-text in ThreeJS to create strings using bitmaps, SDFs and MSDFs. It takes care of multi-line text, and batching all glyphs onto a single geometry. Note that it needs to be installed in a project from npm.

var createGeometry = require('three-bmfont-text')
var loadFont = require('load-bmfont')

loadFont('fonts/Arial.fnt', function(err, font) {
  // create a geometry of packed bitmap glyphs, 
  // word wrapped to 300px and right-aligned
  var geometry = createGeometry({
    font: font,
    text: "My Text"
  })
    
  var textureLoader = new THREE.TextureLoader();
  textureLoader.load('fonts/Arial.png', function (texture) {
    // we can use a simple ThreeJS material
    var material = new THREE.MeshBasicMaterial({
      map: texture,
      transparent: true,
      color: 0xaaffff
    })

    // now do something with our mesh!
    var mesh = new THREE.Mesh(geometry, material)
  })
})

Depending whenever your text is drawn as as full black or full white, use the invert option.

Advantages

  • It’s fast and simple to render.
  • It’s a 1:1 ratio and resolution independent.
  • It can render any string, given the glyphs.
  • It’s good for lots of text that needs to change often.
  • It’s works extremely well with a limited number of glyphs.
  • It’s includes support for things like kerning, line height and word-wrapping at run-time.

Disadvantages

  • It only accepts a limited set of characters and styles.
  • It requires pre-rasterizing glyphs and extra bin packing for optimal usage.
  • It’s blurry and pixelated at large scales, and can also be rotated or transformed.
  • There’s only one quad per rendered glyph.

Interactive Demo: The Shining Credits

Raster bitmap fonts work great for movie credits because we only need a few sizes and styles. The drawback is that the text isn’t great with responsive designs because it’ll look blurry and pixelated at larger sizes.

For the mouse effect, I’m making calculations by mapping the mouse position to the size of the view then calculating the distance from the mouse to the text position. I’m also rotating the text when it hits specific points on the z-axis and y-axis.

Signed distance fields

Much like bitmap fonts, signed distance field (SDF) fonts are also a texture atlas. Unique glyphs are batch into a single „texture atlas” that can create any string at runtime.

But instead of storing the rasterized glyph on the texture the way bitmap fonts do, the glyph’s SDF is generated and stored instead which allows for a high resolution shape from a low resolution image.

Like polygonal meshes (font geometries), SDFs represent shapes. Each pixel on an SDF stores the distance to the closest surface. The sign indicates whenever the pixel is inside or outside the shape. If the sign is negative, then the pixel is inside; if it’s positive, then the pixel is outside. This video illustrates the concept nicely.

SDFs are also commonly used for raytracing and volumetric rendering.

Because an SDF stores distance in each pixel, the raw result looks like a blurry version of the original shape. To output the hard shape you’ll need to alpha test it at 0.5 which is the border of the glyph. Take a look at how the SDF of the letter „A” compares to it’s regular raster image:

Raster text beside of a raw and an alpha tested SDF

As I mentioned earlier, the big benefit of SDFs is being able to render high resolution shapes from low resolution SDF. This means you can create a 16pt font SDF and scale the text up to 100pt or more without losing much crispness.

SDFs are good at scaling because you can almost perfectly reconstruct the distance with bilinear interpolation, which is a fancy way of saying we can get values between two points. In this case, bilinear interpolating between two pixels on a regular bitmap font gives us the in-between color, resulting in a linear blur.

On an SDF, bilinear interpolating between two pixels provides the in-between distance to the nearest edge. Since these two pixel distances are similar to begin with, the resulting value doesn’t lose much information about the glyph. This also means the bigger the SDF, the more accurate and less information is lost.

However, this process comes with a caveat. If the rate change between pixels is not linear — like in the case of sharp corners — bilinear interpolation gives out an inaccurate value, resulting in chipped or rounded corners when scaling an SDF much higher than its original size.

SDF rounded corners

Aside from bumping the texture side, the only real solution is to use multi-channel SDFs, which is what we’ll cover in the next section.

If you want to take a deeper dive into the science behind SDFs, check out the Chris Green’s Master’s thesis (PDF) on the topic.

Advantages

  • They maintain crispness, even when rotated, scaled or transformed.
  • They are ideal for dynamic text.
  • They provide good quality to the size ratio. A single texture can be used to render tiny and huge font sizes without losing much quality.
  • They have a low vertex count of only four vertices per glyph.
  • Antialiasing is inexpensive as is making borders, drop shadows and all kinds of effects with alpha testing.
  • They’re smaller than MSDFs (which we’ll see in a bit).

Disadvantages

  • The can result in rounded or chipped corners when the texture is sampled beyond its resolution. (Again, we’ll see how MSDFs can prevent that.)
  • They’re ineffective at tiny font sizes.
  • They can only be used with monochrome glyphs.

Multi-channel signed distance fields

Multi-channel signed distance field (MSDF) fonts is a bit of a mouthful and a fairly recent variation on SDFs that is capable of producing near-perfect sharp corners by using all three color channels. They do look quite mind blowing at first but don’t let that put you off because they are easy to use than they appear.

A multi-channel signed distance field file can look a little spooky at first.

Using all three color channels does result in a heavier image, but that’s what gives MSDFs a far better quality-to-space ratio than regular SDFs. The following image shows the difference between an SDF and an MSDF for a font that has been scaled up to 50px.

The SDF font results in rounded corners, even at 1x zoom, while the MSDF font retains sharp edges, even at 5x zoom.

Like a regular SDF, an MSDF stores the distance to the nearest edge but changes the color channels whenever it finds a sharp corner. We get the shape by drawing where two color channels or more agree. Although there’s a bit more technique involved. Check out the README for this MSDF generator for a more thorough explanation.

Advantages

  • They support a higher quality and space ratio than SDFs. and are often the better choice.
  • They maintain sharp corners when scaled.

Disadvantages

  • They may contain small artifacts but those can be avoided by bumping up the texture size of the glyph.
  • They requires filtering the median of the three values at runtime which is a bit expensive.
  • They are only compatible with monochrome glyphs.

How to create MSDF fonts

The quickest way to create an MSDF font is to use the msdf-bmfont-web tool. It has most of the relevant customization options and it gets the job done in seconds, all in the browser. Alternatively, there are a number of Google Fonts that have already been converted into MSDF by the folks at A-Frame.

If you are also looking to generate SDFs or your typeface, it requires some special tweaking thanks to some problematic glyphs. The msdf-bmfont-xml CLI gives you a wide range of options, without making things overly confusing. Let’s take a look at how you would use it.

First, you’ll need to install globally it from npm:

npm install msdf-bmfont-xml -g

Next, give it a .ttf font file with your options:

msdf-bmfont ./Open-Sans-Black.ttf --output-type json --font-size 76 --texture-size 512,512

Those options are worth digging into a bit. While msdf-bmfont-xml provides a lot of options to fine-tune your font, there are really just a few options you’ll probably need to correctly generate an MSDF:

  • -t <type> or <code>--field-type <msdf or sdf>: msdf-bmfont-xml generates MSDFs glyph atlases by default. If you want to generate an SDF instead, you need to specify it by using -t sdf.
  • -f <xml or json> or --output-type <xml or json>: msdf-bmfont-xml generates an XML font file that you would have to parse to JSON at runtime. You can avoid this parsing step by exporting JSON straight away.
  • -s, --font-size <fontSize>: Some artifacts and imperfections might show up if the font size is super small. Bumping up the font size will get rid of them most of the time. This example shows a small imperfection in the letter „M.”
  • -m <w,h> or --texture-size <w,h>: If all your characters don’t fit in the same texture, a second texture is created to fit them in. Unless you are trying to take advantage of a multi-page glyph atlas, I recommend increasing the texture size so that it fits over all of the characters on one texture to avoid extra work.

There are other tools that help generate MSDF and SDF fonts:

  • msdf-bmfont-web: A web tool to create MSDFs (but not SDFs) quickly and easily
  • msdf-bmfont: A Node tool using Cairo and node-canvas
  • msdfgen: The original command line tool that all other MSDF tools are based from
  • Hiero: A tool for generating both bitmaps and SDF fonts

How to use SDF and MSDF fonts

Because SDF and MSDF fonts are also glyph atlases, we can use three-bmfont-text like we did for bitmap fonts. The only difference is that we have to get the glyph our of the distance fields with a fragment shader.

Here’s how that works for SDF fonts. Since our distance field has a value greater than .5 outside our glyph and less than 0.5 inside our glyph, we need to alpha test in a fragment shader on each pixel to make sure we only render pixels with a distance less than 0.5, rendering just the inside of the glyphs.

const fragmentShader = `

  uniform vec3 color;
  uniform sampler2D map;
  varying vec2 vUv;
  
  void main(){
    vec4 texColor = texture2D(map, vUv);
    // Only render the inside of the glyph.
    float alpha = step(0.5, texColor.a);

    gl_FragColor = vec4(color, alpha);
    if (gl_FragColor.a < 0.0001) discard;
  }
`;

const vertexShader = `
  varying vec2 vUv;   
  void main {
    gl_Position = projectionMatrix * modelViewMatrix * position;
    vUv = uv;
  }
`;

let material = new THREE.ShaderMaterial({
  fragmentShader, vertexShader,
  uniforms: {
    map: new THREE.Uniform(glyphs),
    color: new THREE.Uniform(new THREE.Color(0xff0000))
  }
})

Similarly, we can import the font from three-bmfont-text which comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:

let SDFShader = require('three-bmfont-text/shaders/sdf');
let material = new THREE.RawShaderMaterial(MSDFShader({
  map: texture,
  transparent: true,
  color: 0x000000
}));

MSDF fonts are a little different. They recreate sharp corners by the intersections of two color channels. Two or more color channels have to agree on it. Before doing any alpha texting, we need to get the median of the three color channels to see where they agree:

const fragmentShader = `

  uniform vec3 color;
  uniform sampler2D map;
  varying vec2 vUv;

  float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
  }
  
  void main(){
    vec4 texColor = texture2D(map, vUv);
    // Only render the inside of the glyph.
    float sigDist = median(texColor.r, texColor.g, texColor.b) - 0.5;
    float alpha = step(0.5, sigDist);
    gl_FragColor = vec4(color, alpha);
    if (gl_FragColor.a < 0.0001) discard;
  }
`;
const vertexShader = `
  varying vec2 vUv;   
  void main {
    gl_Position = projectionMatrix * modelViewMatrix * position;
    vUv = uv;
  }
`;

let material = new THREE.ShaderMaterial({
  fragmentShader, vertexShader,
  uniforms: {
    map: new THREE.Uniform(glyphs),
    color: new THREE.Uniform(new THREE.Color(0xff0000))
  }
})

Again, we can also import from three-bmfont-text using its MSDFShader which also comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:

let MSDFShader = require('three-bmfont-text/shaders/msdf');
let material = new THREE.RawShaderMaterial(MSDFShader({
  map: texture,
  transparent: true,
  color: 0x000000
}));

Demo: Star Wars intro

The Star Wars drawl intro is a good example where MSDF and SDF fonts work well because the effect needs the text to come in multiple sizes. We can use a single MSDF and the text always looks sharp! Although, sadly, three-bm-font doesn’t support justified text yet. Applying left justification would make for a more balanced presentation.

For the light saber effect, I’m raycasting an invisible plane the size of the plane, drawing onto a canvas that’s the same size, and sampling that canvas by mapping the scene position to the texture coordinates.

Bonus tip: Generating 3D text with height maps

Aside from font geometries, all the techniques we’ve covered generate strings or glyphs on a single quad. If you want to build 3D geometries out of a flat texture, your best choice is to use a height map.

A height map is a technique where the geometry height is bumped up using a texture. This is normally used to generate mountains in games, but it turns out to be useful rendering text as well.

The only caveat is that you’ll need a lot of faces for the text to look smooth.

Further reading

Different situations call for different techniques. Nothing we saw here is a silver bullet and they all have their advantages and disadvantages.

There are a lot of tools and libraries out there to help make the most of WebGL text, most of which actually originate from outside WebGL. If you want to keep learning, I highly recommend you go beyond WebGL and check out some of these links:

The post Techniques for Rendering Text with WebGL appeared first on CSS-Tricks.

Techniques for Rendering Text with WebGL

Post pobrano z: Techniques for Rendering Text with WebGL

As is the rule in WebGL, anything that seems like it should be simple is actually quite complicated. Drawing lines, debugging shaders, text rendering… they are all damn hard to do well in WebGL.

Isn’t that weird? WebGL doesn’t have a built-in function for rendering text. Although text seems like the most basic of functionalities. When it comes down to actually rendering it, things get complicated. How do you account for the immense amount of glyphs for every language? How do you work with fixed-width, or proportional-width fonts? What do you do when text needs to be rendered top-to-bottom, left-to-right, or right-to-left? Mathematical equations, diagrams, sheet music?

Suddenly it starts to make sense why text rendering has no place in a low-level graphics API like WebGL. Text rendering is a complex problem with a lot of nuances. If we want to render text, we need to get creative. Fortunately, a lot of smart folks already came up with a wide range of techniques for all our WebGL text needs.

We’ll learn at some of those techniques in this article, including how to generate the assets they need and how to use them with ThreeJS, a JavaScript 3D library that includes a WebGL renderer. As a bonus, each technique is going to have a demo showcasing use cases.

Table of Contents


A quick note on text outside of WebGL

Although this article is all about text inside WebGL, the first thing you should consider is whether you can get away with using HMTL text or canvas overlayed on top of your WebGL canvas. The text can’t be occluded with the 3D geometry as an overlay, but you can get styling and accessibility out of the box. That’s all you need in a lot of cases.

Font geometries

One of the common ways to render text is to build the glyphs with a series of triangles, much like a regular model. After all, rendering points, lines and triangles are a strength of WebGL.

When creating a string, each glyph is made by reading the triangles from a font file of triangulated points. From there, you could extrude the glyph to make it 3D, or scale the glyphs via matrix operations.

Regular font representation (left) and font geometry (right)

Font geometry works best for a small amount of text. That’s because each glyph contains many triangles, which causes drawing to become problematic.

Rendering this exact paragraph you are reading right now with font geometry creates 185,084 triangles and 555,252 vertices. This is just 259 letters. Write the whole article using a font geometry and your computer fan might as well become an airplane turbine.

Although the number of triangles varies by the precision of the triangulation and the typeface in use, rendering lots of text will probably always be a bottleneck when working with font geometry.

How to create a font geometry from a font file

If it were as easy as choosing the font you want and rendering the text. I wouldn’t be writing this article. Regular font formats define glyphs with Bezier curves. On the flip side, drawing those in WebGL is extremely expensive on the CPU and is also complicated to do. If we want to render text, we need to create triangles (triangulation) out of Bezier curves.

I’ve found a few triangulation methods, but by no means are any of them perfect and they may not work with every font. But at least they’ll get you started for triangulating your own typefaces.

Method 1: Automatic and easy

If you are using ThreeJS, you pass your typeface through FaceType.js to read the parametric curves from your font file and put them into a .json file. The font geometry features in ThreeJS take care of triangulating the points for you:

const geometry = new THREE.FontGeometry("Hello There", {font: font, size: 80})

Alternatively, if you are not using ThreeJS and don’t need to have real-time triangulation. You could save yourself the pain of a manual process by using ThreeJS to triangulate the font for you. Then you can extract the vertices and indices from the geometry, and load them in your WebGL application of choice.

Method 2: Manual and painful

The manual option for triangulating a font file is extremely complicated and convoluted, at least initially. It would require a whole article just to explain it in detail. That said, we’ll quickly go over the steps of a basic implementation I grabbed from StackOverflow.

See the Pen
Triangulating Fonts
by Daniel Velasquez (@Anemolo)
on CodePen.

The implementation basically breaks down like this:

  1. Add OpenType.js and Earcut.js to your project.
  2. Get Bezier curves from your .tff font file using OpenType.js.
  3. Convert Bezier curves into closed shapes and sort them by descending area.
  4. Determine the indices for the holes by figuring out which shapes are inside other shapes.
  5. Send all of the points to Earcut with the hole indices as a second parameter.
  6. Use Earcut’s result as the indices for your geometry.
  7. Breath out.

Yeah, it’s a lot. And this implementation may not work for all typefaces. It’ll get you started nonetheless.

Using text geometries in ThreeJS

Thankfully, ThreeJS supports text geometries out of the box. Give it a .json of your favorite font’s Bezier curves and ThreeJS takes care of triangulating the vertices for you in runtime.

var loader = new THREE.FontLoader();
var font;
var text = "Hello World"
var loader = new THREE.FontLoader();
loader.load('fonts/helvetiker_regular.typeface.json', function (helvetiker) {
  font = helvetiker;
  var geometry = new THREE.TextGeometry(text, {
    font: font,
    size: 80,
    height: 5,
  });
}

Advantages

  • It’s easily extruded to create 3D strings.
  • Scaling is made easier with matrix operations.
  • It provides great quality depending on the amount of triangles used.

Disadvantages

  • This doesn’t scale well with large amounts of text due to the high triangle count. Since each character is defined by a lot of triangles, even rendering something as brief as „Hello World” results in 7,396 triangles and 22,188 vertices.
  • This doesn’t lend itself to common text effects.
  • Anti-aliasing depends on your post-processing aliasing or your browser default.
  • Scaling things too big might show the triangles.

Demo: Fading Letters

In the following demo, I took advantage of how easy it is to create 3D text using font geometries. Inside a vertex shader, the extrusion is increased and decreased as time goes on. Pair that with fog and standard material and you get these ghostly letters coming in and out of the void.

Notice how with a low amount of letters the amount of triangles is already in the tens of thousands!

Text (and canvas) textures

Making text textures is probably the simplest and oldest way to draw text in WebGL. Open up Photoshop or some other raster graphics editor, draw an image with some text on it, then render these textures onto a quad and you are done!

Alternatively, you could use the canvas to create the textures on demand at runtime. You’re able to render the canvas as a texture onto a quad as well.

Aside for being the least complicated technique of the bunch. Text textures and canvas textures have the benefit of only needed one quad per texture, or given piece of text. If you really wanted to, you could write the entire British Encyclopedia on a single texture. That way, you only have to render a single quad, six vertices and two faces. Of course, you would do it in a scale, but the idea still remains: You can batch multiple glyphs into same quad. Both text and canvas textures experience have issues with scaling, particularly when working with lots of text.

For text textures, the user has to download all the textures that make up the text, then keep them in memory. For canvas textures, the user doesn’t have to download anything — but now the user’s computer has to do all the rasterizing at runtime, and you need to keep track of where every word is located in the canvas. Plus, updating a big canvas can be really slow.

How to create and use a text texture

Text textures don’t have anything fancy going on for them. Open up your favorite raster graphics editor, draw some text on the canvas and export it as an image. Then you can load it as a texture, and map it on a plane:

// Load texture
let texture = ;
const geometry = new THREE.PlaneBufferGeometry();
const material new THREE.MeshBasicMaterial({map: texture});
this.scene.add(new Mesh(geometry,material));

If your WebGL app has a lot of text, downloading a huge sprite sheet of text might not be ideal, especially for users on slow connections. To avoid the download time, you can rasterize things on demand using an offscreen canvas then sample that canvas as a texture.

Let’s trade download time for performance since rasterizing lots of text takes more than a moment.

function createTextCanvas(string, parameters = {}){
    
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    
    // Prepare the font to be able to measure
    let fontSize = parameters.fontSize || 56;
    ctx.font = `${fontSize}px monospace`;
    
    const textMetrics = ctx.measureText(text);
    
    let width = textMetrics.width;
    let height = fontSize;
    
    // Resize canvas to match text size 
    canvas.width = width;
    canvas.height = height;
    canvas.style.width = width + "px";
    canvas.style.height = height + "px";
    
    // Re-apply font since canvas is resized.
    ctx.font = `${fontSize}px monospace`;
    ctx.textAlign = parameters.align || "center" ;
    ctx.textBaseline = parameters.baseline || "middle";
    
    // Make the canvas transparent for simplicity
    ctx.fillStyle = "transparent";
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    
    ctx.fillStyle = parameters.color || "white";
    ctx.fillText(text, width / 2, height / 2);
    
    return canvas;
}

let texture = new THREE.Texture(createTextCanvas("This is text"));

Now you can use the texture on a plane, like the previous snippet. Or you could create a sprite instead.

As an alternative, you could use more efficient libraries to create texture or sprites, like three-text2d or three-spritetext. And if you want text multi-line text, you should check out this amazing tutorial.

Advantages

  • This provides great 1-to-1 quality with static text.
  • There’s a low vertex/face count. Each string can use as little as six vertices and two faces.
  • It’s easy to implement texture on a quad.
  • It’s fairly trivial to add effects, like borders and glows, using canvas or a graphics editor.
  • Canvas makes it easy to create multi-line text.

Disadvantages

  • Looks blurry if scaled, rotated or transformed after rasterizing.
  • On-non retina, text looks crunchy.
  • You have to rasterize all the strings used. A lot of strings means a lot of data to download.
  • On-demand rasterizing with canvas can be slow if you keep constantly updating the canvas.

Demo: Canvas texture

Canvas textures work well with a limited amount of text that doesn’t change often. So I built a simple wall of text with the quads re-using the same texture.

Bitmap fonts

Both font geometries and text textures experience the same problems handling lots of text. Having one million vertices per piece of text is super inefficient, and creating one texture per piece of text doesn’t really scale.

Bitmap fonts solve this issue by rasterizing all unique glyphs into a single texture, called a texture atlas. This means you can assemble any given string at runtime by creating a quad for each glyph and sampling the section of the texture atlas.

This means users only have to download and use a single texture for all of the text. It also means you only need to render as little as one quad per glyph:

A visual of bitmap font sampling

Rendering this whole article would be approximately 117,272 vertices and 58,636 triangles. That’s 3.1 times more efficient compared to a font geometry rendering just a single paragraph. That a huge improvement!

Because bitmap fonts rasterize the glyph into a texture, they suffer from the same problem as regular images. Zoom in or scale and you start seeing a pixelated and blurry mess. If you want text at a different size, you should send a secondary bitmap with the glyphs on that specific size. Or you could use a Signed Distance Field (SDF) which we’ll cover in the next section.

How to create bitmap fonts

There are a lot of tools to generate bitmaps. Here are some of the more relevant options out there:

  • Angelcode’s bmfont – This is by the creators of the bitmap format.
  • Hiero – This is a Java open-source tool. It’s very similar to Anglecode’s bmfont, but it allows you to add text effects.
  • Glyphs Designer – This is a paid MacOS app.
  • ShoeBox – This is an tool for dealing with sprites, including bitmap fonts.

We’ll use Anglecode’s bmfont for our example because I think it’s the easiest one to get started. At the bottom of this section, you can find other tools if you think it lacks the functionality you are looking for.

When you open the app, you’ll see a screen full of letters that you can select to use.The nice thing about this is that you are able to grab just the glyphs you need instead of sending Greek symbols.

The app’s sidebar allows you to choose and select groups of glyphs.

The BmFont application

Ready to export? Go to OptionsSave bitmap as. And done!

But we’re getting a little ahead of ourselves. Before exporting, there are a few important settings you should check.

Export and Font Option settings
  • Font settings: This let you choose the font and size you want to use. The most important item here is „Match char height.” By default, the app’s „size” option uses pixels instead of points. You’ll see a substantial difference between your graphics editor’s font size and the font size that is generated. Select the „Match char height” options if you want your glyphs to make sense.
  • Export settings: For the export, make sure the texture size is a power of two (e.g. 16×16, 32×32, 64×64, etc.). Then you are able to take advantage of „Linear Mipmap linear” filtering, if needed.

At the bottom of the settings, you’ll see the „file format” section. Choosing either option here is fine as long as you can read the file and create the glyphs.

If you are looking for the smallest file size. I ran a ultra non-scientific test where I created a bitmap of all lowecase and uppercase Latin characters and compared each option. For Font Descriptors, the most efficient format is Binary.

Font Descriptor Format File Size
Binary 3 KB
Raw Text 11 KB
XML 12 KB
Texture Format File Size
PNG 7 KB
Targa 64 KB
DirectDraw Surface 65 KB

PNG is the smallest file size for Text Texture.

Of course, it’s a little more complicated than just file sizes. To get a better idea of which option to use, you should look into parsing time and run-time performance. If you would like to know the pros and cons of each formats, check out this discussion.

How to use bitmap fonts

Creating bitmap font geometry is a bit more involved than just using a texture because we have to construct the string ourselves. Each glyph has its own height and width, and samples a different section of the texture. We have to create a quad for each glyph on our string so we can give them the correct UVs to sample it’s glyph.

You can use three-bmfont-text in ThreeJS to create strings using bitmaps, SDFs and MSDFs. It takes care of multi-line text, and batching all glyphs onto a single geometry. Note that it needs to be installed in a project from npm.

var createGeometry = require('three-bmfont-text')
var loadFont = require('load-bmfont')

loadFont('fonts/Arial.fnt', function(err, font) {
  // create a geometry of packed bitmap glyphs, 
  // word wrapped to 300px and right-aligned
  var geometry = createGeometry({
    font: font,
    text: "My Text"
  })
    
  var textureLoader = new THREE.TextureLoader();
  textureLoader.load('fonts/Arial.png', function (texture) {
    // we can use a simple ThreeJS material
    var material = new THREE.MeshBasicMaterial({
      map: texture,
      transparent: true,
      color: 0xaaffff
    })

    // now do something with our mesh!
    var mesh = new THREE.Mesh(geometry, material)
  })
})

Depending whenever your text is drawn as as full black or full white, use the invert option.

Advantages

  • It’s fast and simple to render.
  • It’s a 1:1 ratio and resolution independent.
  • It can render any string, given the glyphs.
  • It’s good for lots of text that needs to change often.
  • It’s works extremely well with a limited number of glyphs.
  • It’s includes support for things like kerning, line height and word-wrapping at run-time.

Disadvantages

  • It only accepts a limited set of characters and styles.
  • It requires pre-rasterizing glyphs and extra bin packing for optimal usage.
  • It’s blurry and pixelated at large scales, and can also be rotated or transformed.
  • There’s only one quad per rendered glyph.

Interactive Demo: The Shining Credits

Raster bitmap fonts work great for movie credits because we only need a few sizes and styles. The drawback is that the text isn’t great with responsive designs because it’ll look blurry and pixelated at larger sizes.

For the mouse effect, I’m making calculations by mapping the mouse position to the size of the view then calculating the distance from the mouse to the text position. I’m also rotating the text when it hits specific points on the z-axis and y-axis.

Signed distance fields

Much like bitmap fonts, signed distance field (SDF) fonts are also a texture atlas. Unique glyphs are batch into a single „texture atlas” that can create any string at runtime.

But instead of storing the rasterized glyph on the texture the way bitmap fonts do, the glyph’s SDF is generated and stored instead which allows for a high resolution shape from a low resolution image.

Like polygonal meshes (font geometries), SDFs represent shapes. Each pixel on an SDF stores the distance to the closest surface. The sign indicates whenever the pixel is inside or outside the shape. If the sign is negative, then the pixel is inside; if it’s positive, then the pixel is outside. This video illustrates the concept nicely.

SDFs are also commonly used for raytracing and volumetric rendering.

Because an SDF stores distance in each pixel, the raw result looks like a blurry version of the original shape. To output the hard shape you’ll need to alpha test it at 0.5 which is the border of the glyph. Take a look at how the SDF of the letter „A” compares to it’s regular raster image:

Raster text beside of a raw and an alpha tested SDF

As I mentioned earlier, the big benefit of SDFs is being able to render high resolution shapes from low resolution SDF. This means you can create a 16pt font SDF and scale the text up to 100pt or more without losing much crispness.

SDFs are good at scaling because you can almost perfectly reconstruct the distance with bilinear interpolation, which is a fancy way of saying we can get values between two points. In this case, bilinear interpolating between two pixels on a regular bitmap font gives us the in-between color, resulting in a linear blur.

On an SDF, bilinear interpolating between two pixels provides the in-between distance to the nearest edge. Since these two pixel distances are similar to begin with, the resulting value doesn’t lose much information about the glyph. This also means the bigger the SDF, the more accurate and less information is lost.

However, this process comes with a caveat. If the rate change between pixels is not linear — like in the case of sharp corners — bilinear interpolation gives out an inaccurate value, resulting in chipped or rounded corners when scaling an SDF much higher than its original size.

SDF rounded corners

Aside from bumping the texture side, the only real solution is to use multi-channel SDFs, which is what we’ll cover in the next section.

If you want to take a deeper dive into the science behind SDFs, check out the Chris Green’s Master’s thesis (PDF) on the topic.

Advantages

  • They maintain crispness, even when rotated, scaled or transformed.
  • They are ideal for dynamic text.
  • They provide good quality to the size ratio. A single texture can be used to render tiny and huge font sizes without losing much quality.
  • They have a low vertex count of only four vertices per glyph.
  • Antialiasing is inexpensive as is making borders, drop shadows and all kinds of effects with alpha testing.
  • They’re smaller than MSDFs (which we’ll see in a bit).

Disadvantages

  • The can result in rounded or chipped corners when the texture is sampled beyond its resolution. (Again, we’ll see how MSDFs can prevent that.)
  • They’re ineffective at tiny font sizes.
  • They can only be used with monochrome glyphs.

Multi-channel signed distance fields

Multi-channel signed distance field (MSDF) fonts is a bit of a mouthful and a fairly recent variation on SDFs that is capable of producing near-perfect sharp corners by using all three color channels. They do look quite mind blowing at first but don’t let that put you off because they are easy to use than they appear.

A multi-channel signed distance field file can look a little spooky at first.

Using all three color channels does result in a heavier image, but that’s what gives MSDFs a far better quality-to-space ratio than regular SDFs. The following image shows the difference between an SDF and an MSDF for a font that has been scaled up to 50px.

The SDF font results in rounded corners, even at 1x zoom, while the MSDF font retains sharp edges, even at 5x zoom.

Like a regular SDF, an MSDF stores the distance to the nearest edge but changes the color channels whenever it finds a sharp corner. We get the shape by drawing where two color channels or more agree. Although there’s a bit more technique involved. Check out the README for this MSDF generator for a more thorough explanation.

Advantages

  • They support a higher quality and space ratio than SDFs. and are often the better choice.
  • They maintain sharp corners when scaled.

Disadvantages

  • They may contain small artifacts but those can be avoided by bumping up the texture size of the glyph.
  • They requires filtering the median of the three values at runtime which is a bit expensive.
  • They are only compatible with monochrome glyphs.

How to create MSDF fonts

The quickest way to create an MSDF font is to use the msdf-bmfont-web tool. It has most of the relevant customization options and it gets the job done in seconds, all in the browser. Alternatively, there are a number of Google Fonts that have already been converted into MSDF by the folks at A-Frame.

If you are also looking to generate SDFs or your typeface, it requires some special tweaking thanks to some problematic glyphs. The msdf-bmfont-xml CLI gives you a wide range of options, without making things overly confusing. Let’s take a look at how you would use it.

First, you’ll need to install globally it from npm:

npm install msdf-bmfont-xml -g

Next, give it a .ttf font file with your options:

msdf-bmfont ./Open-Sans-Black.ttf --output-type json --font-size 76 --texture-size 512,512

Those options are worth digging into a bit. While msdf-bmfont-xml provides a lot of options to fine-tune your font, there are really just a few options you’ll probably need to correctly generate an MSDF:

  • -t <type> or <code>--field-type <msdf or sdf>: msdf-bmfont-xml generates MSDFs glyph atlases by default. If you want to generate an SDF instead, you need to specify it by using -t sdf.
  • -f <xml or json> or --output-type <xml or json>: msdf-bmfont-xml generates an XML font file that you would have to parse to JSON at runtime. You can avoid this parsing step by exporting JSON straight away.
  • -s, --font-size <fontSize>: Some artifacts and imperfections might show up if the font size is super small. Bumping up the font size will get rid of them most of the time. This example shows a small imperfection in the letter „M.”
  • -m <w,h> or --texture-size <w,h>: If all your characters don’t fit in the same texture, a second texture is created to fit them in. Unless you are trying to take advantage of a multi-page glyph atlas, I recommend increasing the texture size so that it fits over all of the characters on one texture to avoid extra work.

There are other tools that help generate MSDF and SDF fonts:

  • msdf-bmfont-web: A web tool to create MSDFs (but not SDFs) quickly and easily
  • msdf-bmfont: A Node tool using Cairo and node-canvas
  • msdfgen: The original command line tool that all other MSDF tools are based from
  • Hiero: A tool for generating both bitmaps and SDF fonts

How to use SDF and MSDF fonts

Because SDF and MSDF fonts are also glyph atlases, we can use three-bmfont-text like we did for bitmap fonts. The only difference is that we have to get the glyph our of the distance fields with a fragment shader.

Here’s how that works for SDF fonts. Since our distance field has a value greater than .5 outside our glyph and less than 0.5 inside our glyph, we need to alpha test in a fragment shader on each pixel to make sure we only render pixels with a distance less than 0.5, rendering just the inside of the glyphs.

const fragmentShader = `

  uniform vec3 color;
  uniform sampler2D map;
  varying vec2 vUv;
  
  void main(){
    vec4 texColor = texture2D(map, vUv);
    // Only render the inside of the glyph.
    float alpha = step(0.5, texColor.a);

    gl_FragColor = vec4(color, alpha);
    if (gl_FragColor.a < 0.0001) discard;
  }
`;

const vertexShader = `
  varying vec2 vUv;   
  void main {
    gl_Position = projectionMatrix * modelViewMatrix * position;
    vUv = uv;
  }
`;

let material = new THREE.ShaderMaterial({
  fragmentShader, vertexShader,
  uniforms: {
    map: new THREE.Uniform(glyphs),
    color: new THREE.Uniform(new THREE.Color(0xff0000))
  }
})

Similarly, we can import the font from three-bmfont-text which comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:

let SDFShader = require('three-bmfont-text/shaders/sdf');
let material = new THREE.RawShaderMaterial(MSDFShader({
  map: texture,
  transparent: true,
  color: 0x000000
}));

MSDF fonts are a little different. They recreate sharp corners by the intersections of two color channels. Two or more color channels have to agree on it. Before doing any alpha texting, we need to get the median of the three color channels to see where they agree:

const fragmentShader = `

  uniform vec3 color;
  uniform sampler2D map;
  varying vec2 vUv;

  float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
  }
  
  void main(){
    vec4 texColor = texture2D(map, vUv);
    // Only render the inside of the glyph.
    float sigDist = median(texColor.r, texColor.g, texColor.b) - 0.5;
    float alpha = step(0.5, sigDist);
    gl_FragColor = vec4(color, alpha);
    if (gl_FragColor.a < 0.0001) discard;
  }
`;
const vertexShader = `
  varying vec2 vUv;   
  void main {
    gl_Position = projectionMatrix * modelViewMatrix * position;
    vUv = uv;
  }
`;

let material = new THREE.ShaderMaterial({
  fragmentShader, vertexShader,
  uniforms: {
    map: new THREE.Uniform(glyphs),
    color: new THREE.Uniform(new THREE.Color(0xff0000))
  }
})

Again, we can also import from three-bmfont-text using its MSDFShader which also comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:

let MSDFShader = require('three-bmfont-text/shaders/msdf');
let material = new THREE.RawShaderMaterial(MSDFShader({
  map: texture,
  transparent: true,
  color: 0x000000
}));

Demo: Star Wars intro

The Star Wars drawl intro is a good example where MSDF and SDF fonts work well because the effect needs the text to come in multiple sizes. We can use a single MSDF and the text always looks sharp! Although, sadly, three-bm-font doesn’t support justified text yet. Applying left justification would make for a more balanced presentation.

For the light saber effect, I’m raycasting an invisible plane the size of the plane, drawing onto a canvas that’s the same size, and sampling that canvas by mapping the scene position to the texture coordinates.

Bonus tip: Generating 3D text with height maps

Aside from font geometries, all the techniques we’ve covered generate strings or glyphs on a single quad. If you want to build 3D geometries out of a flat texture, your best choice is to use a height map.

A height map is a technique where the geometry height is bumped up using a texture. This is normally used to generate mountains in games, but it turns out to be useful rendering text as well.

The only caveat is that you’ll need a lot of faces for the text to look smooth.

Further reading

Different situations call for different techniques. Nothing we saw here is a silver bullet and they all have their advantages and disadvantages.

There are a lot of tools and libraries out there to help make the most of WebGL text, most of which actually originate from outside WebGL. If you want to keep learning, I highly recommend you go beyond WebGL and check out some of these links:

The post Techniques for Rendering Text with WebGL appeared first on CSS-Tricks.

CSS Architecture for Modern JavaScript Applications

Post pobrano z: CSS Architecture for Modern JavaScript Applications

There is a lot to like from Mike Riethmuller here:

  • The title. When you’re building a website from JavaScript-powered components anyway, that is a moment to talk about how to do styling, because it opens some doors to JavaScript-powered styles that you probably wouldn’t otherwise choose.
  • The personal experience and pragmatism. Drawing on five years of consulting, he’s seeing that component re-use and style understandability is suffering, not improving, partly due to every team having different approaches. He says „it’s a little bit of everybody’s fault” and sees the perspective of others who like parts of what JavaScript-powered styles can bring, like less dependence on specificity.
  • The fresh thinking. Since JavaScript-powered websites are all built by nested components anyway, why not use that architecture for styling? The thesis of the article is really about building UI components that, on purpose, don’t involve application logic but exist just for styling, and using a combination of clever CSS and JavaScript power to the kind of styling you need.

Direct Link to ArticlePermalink

The post CSS Architecture for Modern JavaScript Applications appeared first on CSS-Tricks.

Web Scraping Made Simple With Zenscrape

Post pobrano z: Web Scraping Made Simple With Zenscrape

Web scraping has always been taken care of by actual developers, since a lot of coding, proxy management and CAPTCHA-solving is involved. However, the scraped data is very often needed by people that are non-coders: Marketers, Analysts, Business Developers etc.

Zenscrape is an easy-to-use web scraping tool that allows people to scrape websites without having to code.

Let’s run through a quick example together:

Select the data you need

The setup wizard guides you through the process of setting up your data extractor. It allows you to select the information you want to scrape visually. Click on the desired piece of content and specify what type of element you have. Depending on the package you have bought (they also offer a free plan), you can select up to 30 data elements per page.

The scraper is also capable of handling element lists.

Schedule your extractor

Perhaps, you want to scrape the selected data at a specific time interval. Depending on your plan, you can choose any time span between one minute to one hour. Also, decide what is supposed to happen with the scraped data after it has been gathered.

Use your data

In this example, we have chosen the .csv-export method and have selected a 10 minute scraping interval. Our first set of data should be ready by now. Let’s take a look:

Success! Our data is ready for us to be downloaded. We can now access all individual data sets or download all previously gathered data at once, in one file.

Need more flexibility?

Zenscrape also offers a web scraping API that returns the HTML markup of any website. This is especially useful for complicated scraping projects, that require the scraped content to be integrated into a software application for further processing.

Just like the web scraping suite, the API does not forward failed requests and takes care of proxy management, CAPTCHA-solving and all other maintenance tasks that are usually involved with DIY web scrapers.

Since the API returns the full HTML markup of the related website, you have full flexibility in terms of data selection and further processing.

Try Zenscrape

The post Web Scraping Made Simple With Zenscrape appeared first on CSS-Tricks.

The Power (and Fun) of Scope with CSS Custom Properties

Post pobrano z: The Power (and Fun) of Scope with CSS Custom Properties

You’re probably already at least a little familiar with CSS variables. If not, here’s a two-second overview: they are really called custom properties, you set them in declaration blocks like --size: 1em and use them as values like font-size: var(--size);, they differ from preprocessor variables (e.g. they cascade), and here’s a guide with way more information.

But are we using them to their full potential? Do we fall into old habits and overlook opportunities where variables could significantly reduce the amount of code we write?

This article was prompted by a recent tweet I made about using CSS variables to create dynamic animation behavior.

CSS variables are awesome, right? But scope power is often overlooked. For example, take this demo, 3 different animations but only 1 animation defined 💪 That means dynamic animations 😎 https://t.co/VN02NlC4G8 via @CodePen #CSS #animation #webdev #webdesign #coding pic.twitter.com/ig8baxr7F3

— Jhey @ NodeConfEU 2019 📍🇮🇪⌨️ (@jh3yy) November 5, 2019

Let’s look at a couple of instances where CSS variables can be used to do some pretty cool things that we may not have considered.

Basic scoping wins

The simplest and likely most common example would be scoped colors. And what’s our favorite component to use with color? The button. 😅

Consider the standard setup of primary and secondary buttons. Let’s start with some basic markup that uses a BEM syntax.

<button class="button button--primary">Primary</button>
<button class="button button--secondary">Secondary</button>

Traditionally, we might do something like this to style them up:

.button {
  padding: 1rem 1.25rem;
  color: #fff;
  font-weight: bold;
  font-size: 1.25rem;
  margin: 4px;
  transition: background 0.1s ease;
}

.button--primary {
  background: hsl(233, 100%, 50%);
  outline-color: hsl(233, 100%, 80%);
}

.button--primary:hover {
  background: hsl(233, 100%, 40%);
}

.button--primary:active {
  background: hsl(233, 100%, 30%);
}

.button--secondary {
  background: hsl(200, 100%, 50%);
  outline-color: hsl(200, 100%, 80%);
}

.button--secondary:hover {
  background: hsl(200, 100%, 40%);
}

.button--secondary:active {
  background: hsl(200, 100%, 30%);
}

See the Pen
Basic buttons
by Jhey (@jh3y)
on CodePen.

That’s an awful lot of code for something not particularly complex. We haven’t added many styles and we’ve added a lot of rules to cater to the button’s different states and colors. We could significantly reduce the code with a scoped variable.

In our example, the only differing value between the two button variants is the hue. Let’s refactor that code a little then. We won’t change the markup but cleaning up the styles a little, we get this:

.button {
  padding: 1rem 1.25rem;
  color: #fff;
  font-weight: bold;
  font-size: 1.25rem;
  margin: 1rem;
  transition: background 0.1s ease;
  background: hsl(var(--hue), 100%, 50%);
  outline-color: hsl(var(--hue), 100%, 80%);

}
.button:hover {
  background: hsl(var(--hue), 100%, 40%);
}

.button:active {
  background: hsl(var(--hue), 100%, 30%);
}

.button--primary {
  --hue: 233;
}

.button--secondary {
  --hue: 200;
}

See the Pen
Refactoring styles with a scoped variable
by Jhey (@jh3y)
on CodePen.

This not only reduces the code but makes maintenance so much easier. Change the core button styles in one place and it will update all the variants! 🙌

I’d likely leave it there to make it easier for devs wanting to use those buttons. But, we could take it further. We could inline the variable on the actual element and remove the class declarations completely. 😲

<button class="button" style="--hue: 233;">Primary</button>
<button class="button" style="--hue: 200;">Secondary</button>

Now we don’t need these. 👍

.button--primary {
  --hue: 233;
}

.button--secondary {
  --hue: 200;
}

See the Pen
Scoping w/ inline CSS variables
by Jhey (@jh3y)
on CodePen.

Inlining those variables might not be best for your next design system or app but it does open up opportunities. Like, for example, if we had a button instance where we needed to override the color.

button.button.button--primary(style=`--hue: 20;`) Overridden

See the Pen
Overridden with inline scope
by Jhey (@jh3y)
on CodePen.

Having fun with inline variables

Another opportunity is to have a little fun with it. This is a technique I use for many of the Pens I create over on CodePen. 😉

You may be writing straightforward HTML, but in many cases, you may be using a framework, like React or a preprocessor like Pug, to write your markup. These solutions allow you to leverage JavaScript to create random inline variables. For the following examples, I’ll be using Pug. Pug is an indentation-based HTML templating engine. If you aren’t familiar with Pug, do not fear! I’ll try to keep the markup simple.

Let’s start by randomizing the hue for our buttons:

button.button(style=`--hue: ${Math.random() * 360}`) First

With Pug, we can use ES6 template literals to inline randomized CSS variables. 💪

See the Pen
Random inline CSS variable hues
by Jhey (@jh3y)
on CodePen.

Animation alterations

So, now that we have the opportunity to define random characteristics for an element, what else could we do? Well, one overlooked opportunity is animation. True, we can’t animate the variable itself, like this:

@keyframes grow {
  from { --scale: 1; }
  to   { --scale: 2; }
}

But we can create dynamic animations based on scoped variables. We can change the behavior of animation on the fly! 🤩

Example 1: The excited button

Let’s create a button that floats along minding its own business and then gets excited when we hover over it.

Start with the markup:

button.button(style=`--hue: ${Math.random() * 360}`) Show me attention

A simple floating animation may look like this:

@keyframes flow {
  0%, 100% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(0, -25%);
  }
}

This will give us something like this:

See the Pen
The excited button foundation
by Jhey (@jh3y)
on CodePen.

I’ve added a little shadow as an extra but it’s not vital. 👍

Let’s make it so that our button gets excited when we hover over it. Now, we could simply change the animation being used to something like this:

.button:hover {
  animation: shake .1s infinite ease-in-out;
}

@keyframes shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  25% {
    transform: translate(-1%, 3%) rotate(-2deg);
  }
  50% {
    transform: translate(1%, 2%) rotate(2deg);
  }
  75% {
    transform: translate(1%, -2%) rotate(-1deg);
  }
}

And it works:

See the Pen
The excited button gets another keyframes definition
by Jhey (@jh3y)
on CodePen.

But, we need to introduce another keyframes definition. What if we could merge the two animations into one? They aren’t too far off from each other in terms of structure.

We could try:

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  25%, 75% {
    transform: translate(0, -12.5%) rotate(0deg);
  }
  50% {
    transform: translate(0, -25%) rotate(0deg);
  }
}

Although this works, we end up with an animation that isn’t quite as smooth because of the translation steps. So what else could we do? Let’s find a compromise by removing the steps at 25% and 75%.

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  50% {
    transform: translate(0, -25%) rotate(0deg);
  }
}

It works fine, as we expected, but here comes the trick: Let’s update our button with some variables.

.button {
  --y: -25;
  --x: 0;
  --rotation: 0;
  --speed: 2;
}

Now let’s plug them into the animation definition, along with the button’s animation properties.

.button {
  animation-name: flow-and-shake;
  animation-duration: calc(var(--speed) * 1s);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(calc(var(--x) * -1%), calc(var(--y) * -1%))
      rotate(calc(var(--rotation) * -1deg));
  }
  50% {
    transform: translate(calc(var(--x) * 1%), calc(var(--y) * 1%))
      rotate(calc(var(--rotation) * 1deg));
  }
}

All is well. 👍

Let’s change those values when the button is hovered:

.button:hover {
  --speed: .1;
  --x: 1;
  --y: -1;
  --rotation: -1;
}

See the Pen
The excited button with refactored keyframes & scoped variables
by Jhey (@jh3y)
on CodePen.

Nice! Now our button has two different types of animations but defined via one set of keyframes. 🤯

Let’s have a little more fun with it. If we take it a little further, we can make the button a little more playful and maybe stop animating altogether when it’s active. 😅

See the Pen
The Excited Button w/ dynamic animation 🤓
by Jhey (@jh3y)
on CodePen.

Example 2: Bubbles

Now that we’ve gone through some different techniques for things we can do with the power of scope, let’s put it all together. We are going to create a randomly generated bubble scene that heavily leverages scoped CSS variables.

Let’s start by creating a bubble. A static bubble.

.bubble {
  background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),
    radial-gradient(15% 15% at 75% 75%, #80dfff, transparent),
    radial-gradient(100% 100% at 50% 25%, transparent, #66d9ff 98%);
  border: 1px solid #b3ecff;
  border-radius: 100%;
  height: 50px;
  width: 50px;
}

We are using background with multiple values and a border to make the bubble effect — but it’s not very dynamic. We know the border-radius will always be the same. And we know the structure of the border and background will not change. But the values used within those properties and the other property values could all be random.

Let’s refactor the CSS to make use of variables:

.bubble {
  --size: 50;
  --hue: 195;
  --bubble-outline: hsl(var(--hue), 100%, 50%);
  --bubble-spot: hsl(var(--hue), 100%, 75%);
  --bubble-shade: hsl(var(--hue), 100%, 70%);
  background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),
    radial-gradient(15% 15% at 75% 75%, var(--bubble-spot), transparent),
    radial-gradient(100% 100% at 50% 25%, transparent, var(--bubble-shade) 98%);
  border: 1px solid var(--bubble-outline);
  border-radius: 100%;
  height: calc(var(--size) * 1px);
  width: calc(var(--size) * 1px);
}

That’s a good start. 👍

See the Pen
Bubbles foundation
by Jhey (@jh3y)
on CodePen.

Let’s add some more bubbles and leverage the inline scope to position them as well as size them. Since we are going to start randomizing more than one value, it’s handy to have a function to generate a random number in range for our markup.

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min

With Pug, we can utilize iteration to create a large set of bubbles:

- const baseHue = randomInRange(0, 360)
- const bubbleCount = 50
- let b = 0
while b < bubbleCount
  - const size = randomInRange(10, 50)
  - const x = randomInRange(0, 100)
  .bubble(style=`--x: ${x}; --size: ${size}; --hue: ${baseHue}`)
  - b++

Updating our .bubble styling allows us to make use of the new inline variables.

.bubble {
  left: calc(var(--x) * 1%);
  position: absolute;
  transform: translate(-50%, 0);
}

Giving us a random set of bubbles:

See the Pen
Adding bubbles
by Jhey (@jh3y)
on CodePen.

Let’s take it even further and animate those bubbles so they float from top to bottom and fade out.

.bubble {
  animation: float 5s infinite ease-in-out;
  top: 100%;
}

@keyframes float {
  from {
    opacity: 1;
    transform: translate(0, 0) scale(0);
  }
  to {
    opacity: 0;
    transform: translate(0, -100vh) scale(1);
  }
}

See the Pen
Bubbles rising together
by Jhey (@jh3y)
on CodePen.

That’s pretty boring. They all do the same thing at the same time. So let’s randomize the speed, delay, end scale and distance each bubble is going to travel.

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min
- const baseHue = randomInRange(0, 360)
- const bubbleCount = 50
- let b = 0
while b < bubbleCount
  - const size = randomInRange(10, 50)
  - const delay = randomInRange(1, 10)
  - const speed = randomInRange(2, 20)
  - const distance = randomInRange(25, 150)
  - const scale = randomInRange(100, 150) / 100
  - const x = randomInRange(0, 100)
  .bubble(style=`--x: ${x}; --size: ${size}; --hue: ${baseHue}; --distance: ${distance}; --speed: ${speed}; --delay: ${delay}; --scale: ${scale}`)
  - b++

And now, let’s update our styles

.bubble {
  animation-name: float;
  animation-duration: calc(var(--speed) * 1s);
  animation-delay: calc(var(--delay) * -1s);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

@keyframes float {
  from {
    opacity: 1;
    transform: translate(-50%, 0) scale(0);
  }
  to {
    opacity: 0;
    transform: translate(-50%, calc(var(--distance) * -1vh)) scale(var(--scale));
  }
}

And we will get this:

See the Pen
Random bubble scene using variable scope 😎
by Jhey (@jh3y)
on CodePen.

With around 50 lines of code, you can create a randomly generated animated scene by honing the power of the scope! 💪

That’s it!

We can create some pretty cool things with very little code by putting CSS variables to use and leveraging some little tricks.

I do hope this article has raised some awareness for the power of CSS variable scope and I do hope you will hone the power and pass it on 😎

All the demos in this article are available in this CodePen collection.

The post The Power (and Fun) of Scope with CSS Custom Properties appeared first on CSS-Tricks.

Simplified Fluid Typography

Post pobrano z: Simplified Fluid Typography

Fluid typography is the idea that font-size (and perhaps other attributes of type, like line-height) change depending on the screen size (or perhaps container queries if we had them).

The core trickery comes from viewport units. You can literally set type in viewport units (e.g. font-size: 4vw), but the fluctuations in size are so extreme that it’s usually undesirable. That’s tampered by doing something like font-size: calc(16px + 1vw). But while we’re getting fancy with calculations anyway, the most common implementation ended up being an equation to calculate plain English:

I want the type to go between being 16px on a 320px screen to 22px on a 1000px screen.

Which ended up like this:

html {
  font-size: 16px;
}
@media screen and (min-width: 320px) {
  html {
    font-size: calc(16px + 6 * ((100vw - 320px) / 680));
  }
}
@media screen and (min-width: 1000px) {
  html {
    font-size: 22px;
  }
} 

That’s essentially setting a minimum and maximum font size so the type won’t shrink or grow to anything too extreme. „CSS locks” was a term coined by Tim Brown.

Minimum and maximum you say?! Well it so happens that functions for these have made their way into the CSS spec in the form of min() and max().

So we can simplify our fancy setup above with a one-liner and maintain the locks:

html {
  font-size: min(max(16px, 4vw), 22px);
}

We actually might want to stop there because even though both Safari (11.1+) and Chrome (79+) support this at the current moment, that’s as wide as support will get today. Speaking of which, you’d probably want to slip a font-size declaration before this to set an acceptable fallback value with no fancy functions.

But as long as we’re pushing the limits, there is another function to simplify things even more: clamp()! Clamp takes three values, a min, max, and a flexible unit (or calculation or whatever) in the middle that it will use in case the value is between the min and max. So, our one-liner gets even smaller:

body {
  font-size: clamp(16px, 4vw, 22px);
} 

That’ll be Chrome 79+ (which doesn’t hasn’t even dropped to stable but will very soon).

Uncle Dave is very happy that FitText is now a few bytes instead of all-of-jQuery plus 40 more lines. Here is us chucking CSS custom properties at it:

See the Pen
FitText in CSS with clamp()
by Dave Rupert (@davatron5000)
on CodePen.

The post Simplified Fluid Typography appeared first on CSS-Tricks.

Testing React Hooks With Enzyme and React Testing Library

Post pobrano z: Testing React Hooks With Enzyme and React Testing Library

As you begin to make use of React hooks in your applications, you’ll want to be certain the code you write is nothing short of solid. There’s nothing like shipping buggy code. One way to be certain your code is bug-free is to write tests. And testing React hooks is not much different from how React applications are tested in general.

In this tutorial, we will look at how to do that by making use of a to-do application built with hooks. We’ll cover writing of tests using Ezyme and React Testing Library, both of which are able to do just that. If you’re new to Enzyme, we actually posted about it a little while back showing how it can be used with Jest in React applications. It’s not a bad idea to check that as we dig into testing React hooks.

Here’s what we want to test

A pretty standard to-do component looks something like this:

import React, { useState, useRef } from "react";
const Todo = () => {
  const [todos, setTodos] = useState([
    { id: 1, item: "Fix bugs" },
    { id: 2, item: "Take out the trash" }
  ]);
  const todoRef = useRef();
  const removeTodo = id => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  const addTodo = data => {
    let id = todos.length + 1;
    setTodos([
      ...todos,
      {
        id,
        item: data
      }
    ]);
  };
  const handleNewTodo = e => {
    e.preventDefault();
    const item = todoRef.current;
    addTodo(item.value);
    item.value = "";
  };
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <h2>Add Todo</h2>
        </div>
      </div>
      <form>
        <div className="row">
          <div className="col-md-6">
            <input
              type="text"
              autoFocus
              ref={todoRef}
              placeholder="Enter a task"
              className="form-control"
              data-testid="input"
            />
          </div>
        </div>
        <div className="row">
          <div className="col-md-6">
            <button
              type="submit"
              onClick={handleNewTodo}
              className="btn btn-primary"
            >
              Add Task
            </button>
          </div>
        </div>
      </form>
      <div className="row todo-list">
        <div className="col-md-6">
          <h3>Lists</h3>
          {!todos.length ? (
            <div className="no-task">No task!</div>
          ) : (
            <ul data-testid="todos">
              {todos.map(todo => {
                return (
                  <li key={todo.id}>
                    <div>
                      <span>{todo.item}</span>
                      <button
                        className="btn btn-danger"
                        data-testid="delete-button"
                        onClick={() => removeTodo(todo.id)}
                      >
                        X
                      </button>
                    </div>
                  </li>
                );
              })}
            </ul>
          )}
        </div>
      </div>
    </div>
  );
};
export default Todo; 

Testing with Enzyme

We need to install the packages before we can start testing. Time to fire up the terminal!

npm install --save-dev enzyme enzyme-adapter-16 

Inside the src directory, create a file called setupTests.js. This is what we’ll use to configure Enzyme’s adapter.

import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() }); 

Now we can start writing our tests! We want to test four things:

  1. That the component renders
  2. That the initial to-dos get displayed when it renders
  3. That we can create a new to-do and get back three others
  4. That we can delete one of the initial to-dos and have only one to-do left

In your src directory, create a folder called __tests__ and create the file where you’ll write your Todo component’s tests in it. Let’s call that file Todo.test.js.

With that done, we can import the packages we need and create a describe block where we’ll fill in our tests.

import React from "react";
import { shallow, mount } from "enzyme";
import Todo from "../Todo";

describe("Todo", () => {
  // Tests will go here using `it` blocks
});

Test 1: The component renders

For this, we’ll make use of shallow render. Shallow rendering allows us to check if the render method of the component gets called — that’s what we want to confirm here because that’s the proof we need that the component renders.

it("renders", () => {
  shallow(<Todo />);
});

Test 2: Initial to-dos get displayed

Here is where we’ll make use of the mount method, which allows us to go deeper than what shallow gives us. That way, we can check the length of the to-do items.

it("displays initial to-dos", () => {
  const wrapper = mount(<Todo />);
  expect(wrapper.find("li")).toHaveLength(2);
});

Test 3: We can create a new to-do and get back three others

Let’s think about the process involved in creating a new to-do:

  1. The user enters a value into the input field.
  2. The user clicks the submit button.
  3. We get a total of three to-do items, where the third is the newly created one.
it("adds a new item", () => {
  const wrapper = mount(<Todo />);
  wrapper.find("input").instance().value = "Fix failing test";
  expect(wrapper.find("input").instance().value).toEqual("Fix failing test");
  wrapper.find('[type="submit"]').simulate("click");
  expect(wrapper.find("li")).toHaveLength(3);
  expect(
    wrapper
      .find("li div span")
      .last()
      .text()
  ).toEqual("Fix failing test");
});

We mount the component then we make use of find() and instance() methods to set the value of the input field. We assert that the value of the input field is set to “Fix failing test” before going further to simulate a click event, which should add the new item to the to-do list.

We finally assert that we have three items on the list and that the third item is equal to the one we created.

Test 4: We can delete one of the initial to-dos and have only one to-do left

it("removes an item", () => {
  const wrapper = mount(<Todo />);
  wrapper
    .find("li button")
    .first()
    .simulate("click");
  expect(wrapper.find("li")).toHaveLength(1);
  expect(wrapper.find("li span").map(item => item.text())).toEqual([
    "Take out the trash"
  ]);
});

In this scenario, we return the to-do with a simulated click event on the first item. It’s expected that this will call the removeTodo() method, which should delete the item that was clicked. Then we’re checking the numbers of items we have, and the value of the one that gets returned.

The source code for these four tests are here on GitHub for you to check out.

Testing With react-testing-library

We’ll write three tests for this:

  1. That the initial to-do renders
  2. That we can add a new to-do
  3. That we can delete a to-do

Let’s start by installing the packages we need:

npm install --save-dev @testing-library/jest-dom @testing-library/react

Next, we can import the packages and files:

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Todo from "../Todo";
import "@testing-library/jest-dom/extend-expect";

test("Todo", () => {
  // Tests go here
}

Test 1: The initial to-do renders

We’ll write our tests in a test block. The first test will look like this:

it("displays initial to-dos", () => {
  const { getByTestId } = render(<Todo />);
  const todos = getByTestId("todos");
  expect(todos.children.length).toBe(2);
});

What’s happening here? We’re making use of getTestId to return the node of the element where data-testid matches the one that was passed to the method. That’s the <ul> element in this case. Then, we’re checking that it has a total of two children (each child being a <li> element inside the unordered list). This will pass as the initial to-do is equal to two.

Test 2: We can add a new to-do

We’re also making use of getTestById here to return the node that matches the argument we’re passing in.

it("adds a new to-do", () => {
  const { getByTestId, getByText } = render(<Todo />);
  const input = getByTestId("input");
  const todos = getByTestId("todos");
  input.value = "Fix failing tests";
  fireEvent.click(getByText("Add Task"));
  expect(todos.children.length).toBe(3);
});

We use getByTestId to return the input field and the ul element like we did before. To simulate a click event that adds a new to-do item, we’re using fireEvent.click() and passing in the getByText() method, which returns the node whose text matches the argument we passed. From there, we can then check to see the length of the to-dos by checking the length of the children array.

Test 3: We can delete a to-do

This will look a little like what we did a little earlier:

it("deletes a to-do", () => {
  const { getAllByTestId, getByTestId } = render(<Todo />);
  const todos = getByTestId("todos");
  const deleteButton = getAllByTestId("delete-button");
  const first = deleteButton[0];
  fireEvent.click(first);
  expect(todos.children.length).toBe(1);
});

We’re making use of getAllByTestId to return the nodes of the delete button. Since we only want to delete one item, we fire a click event on the first item in the collection, which should delete the first to-do. This should then make the length of todos children equal to one.

These tests are also available on GitHub.

Linting

There are two lint rules to abide by when working with hooks:

Rule 1: Call hooks at the top level

…as opposed to inside conditionals, loops or nested functions.

// Don't do this!
if (Math.random() > 0.5) {
  const [invalid, updateInvalid] = useState(false);
}

This goes against the first rule. According to the official documentation, React depends on the order in which hooks are called to associate state and the corresponding useState call. This code breaks the order as the hook will only be called if the conditions are true.

This also applies to useEffect and other hooks. Check out the documentation for more details.

Rule 2: Call hooks from React functional components

Hooks are meant to be used in React functional components — not in React’s class component or a JavaScript function.

We’ve basically covered what not to do when it comes to linting. We can avoid these missteps with an npm package that specifically enforces these rules.

npm install eslint-plugin-react-hooks --save-dev

Here’s what we add to the package’s configuration file to make it do its thing:

{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

If you are making use of Create React App, then you should know that the package supports the lint plugin out of the box as of v3.0.0.

Go forth and write solid React code!

React hooks are equally prone to error as anything else in your application and you’re gonna want to ensure that you use them well. As we just saw, there’s a couple of ways we can go about it. Whether you use Enzyme or You can either make use of enzyme or React Testing Library to write tests is totally up to you. Either way, try making use of linting as you go, and no doubt, you’ll be glad you did.

The post Testing React Hooks With Enzyme and React Testing Library appeared first on CSS-Tricks.

We are Programmers

Post pobrano z: We are Programmers

Building websites is programming. Writing HTML and CSS is programming. I am a programmer, and if you’re here, reading CSS-Tricks, chances are you’re a programmer, too.

The thing is, the details in programming layout with CSS are different, for example, than the details in programming API endpoints with Ruby. Or machine learning with Python. Or programming a browser engine with C++.

But those differences are details! A lot of details, but still… details. It’s all programming.

I see programmers like this:

An illustration of three scenes, each with a monsters doing a different looking puzzle. In each scene, the moster is labeled as a programmer, and the puzzle is labeled as the programming language. The first scene is C++, the second JavaScript, and the third, Python.

Where do HTML and CSS fit into this weird and cute universe? What is it to program user interface on the web?

Programming boxes, I like to say. Everything is a box, and as HTML/CSS programmers, we program boxes within the domain of the browser. Like this:

Another illustration with a scene where a monster is working on a puzzle, but this puzzle is clearly made of layers of boxes. It is inside a blue square that is labeled 'Browser', and the monster doing the puzzle is very cute with three eyes. It is labeled Box Programmer.

Cute. So?

So…I believe that we, both as individual programmers and together, as the web slice of the tech industry, need to arrive at a more holistic and inclusive understanding of what it means to be a programmer. This outlook not only makes tech a more welcoming place, but it makes us programmers more powerful and more adaptable.

To me – well, me in 2019 – programming is writing1 instructions for computers that other programmers, such as your future self, are able to read and maintain. As programmer, I am confident that, once I know one language well, I can learn another one2. At the end of the day, it’s all made of the same stuff.

And yet…

I have been a programmer in this sense for around eight years, but up until about two years ago, I didn’t see myself as one. In fact, I was actively opposed to calling myself a programmer, and in recent times I’ve heard the same sentiment from others. Why, exactly? Is this a reaction to the „not real programming” phenomenon? Is that still happening? What are the impacts? What were the impacts, for me and for others?

Yes, I know 'gatekeeping’ – that is, the self-inflating exclusion of others from a community or identity – is a thing, and that some people are just jerks, but I think there is more to this story.

So, what’s interesting to me3 about building websites this year? Talking to others who build websites4 and beginning the process of answering these burning questions.


  1. I highly recommend reading Programming is writing is programming, a post and research paper by Felienne
  2. Box programmers: Do they know things? What do they know? Let’s find out!! In 2020, my goal is to learn learn Rust, a low-level programming language similar to C++. Correction: my goal is to start learning Rust – that is more than a one year undertaking. Why Rust? Keep an eye on my blog, I’ll write more about this soon enough. 
  3. It was hard to choose what to write about for this post! I’m interested in a lot of things, specifically unit testing CSS, my job as a Design Engineer, and exploring/sharing more about CSS algorithms.
  4. What do you think, CSS-Tricks reader? Do you call yourself a programmer? Why, or why not? Have you experienced this „not real programming” phenomenon? How did it impact you? Feel free to write me a Twitter message or send me an email.

The post We are Programmers appeared first on CSS-Tricks.

The New Good Ol’ Days

Post pobrano z: The New Good Ol’ Days

Eighteen years into this game, I love to reminisce back to the good ol’ days of the early to mid-2000s when there was an explosion of creativity on the web. It felt fresh and unbridled, with boundaries expected to be pushed at every turn, and they were. This was mainly down to one thing, the thing of nightmares to some, Flash! It, of course, had some big inherent flaws, but love it or hate it, certainly helped pave the way for what we expected from the open web. Sure, it was probably a more drawn-out process than we’d hoped for, but with some savage new advancements made over the last few years, I now feel that things are really starting to get proper juicy.

Several things come to mind that get me excited these days in designing and developing for the web, some widely adopted now like SVG (even though technically old) and WebGL animation, which draw some obvious parallels to the Flash era. Certainly, CSS Grid stood up and made itself known, shouting from the parapets about how the shackles of layout are now a thing of the past. Kudos to awesome people like Rachel Andrew and Jen Simmons for their serious efforts in educating us mere mortals in this area, helping make the learning curve and adoption that bit more palatable and accessible.

Looking around today you can see how advancements like Grid have helped elevate the rise of more asymmetric layouts, with design styles like Brutalism design going through a bit of a trend over the last year or so. But over time it felt as though typography might have taken the back seat a tad with all the other successes happening in pushing the web forward. Now enter variable fonts 🥳

Variable fonts have certainly piqued my interest this past year or so. Not only do they give us the obvious boost in performance with fewer https requests and smaller sizes compared to the bundles of web fonts we all inject into our pages, but we also gain more control over typography in terms of readability and accessibility. Say goodbye to cheekily adding muddy sacrificial faux bold or italic styles to our CSS!

Taking this further, I feel that variable fonts have really unlocked the door to new creative possibilities that we’re only just scratching the surface of. Having the ability to interpolate between different values of the axes just screams out for animation. We have the standardized set of 5 registered axes like font-weight, font-stretch, font-style, etc. which are straight forward enough to appreciate, but when it comes to custom axes via font-variation-settings, things start getting crazy and fun. Type designers can use the interpolation in custom axes to create some really off the wall things beyond, well… text. Just check out Typearture’s fab variable font experiments to see what I mean.

A few months back I was privileged to be invited to experiment with and test Greensock’s newly released GSAP 3, which I’m also most definitely excited about. It boasts a new simplified API and 50+ new features, yet is only about half the file size as the previous version. This is currently my catnip though: layering GSAP on top of variable fonts in CodePen to create some lovely kinetic typography in the DOM. This kind of magic, until now I guess, has been more attributed to WebGL, Processing and After Effects. Now, throw some GSAP plugins on top again, and you can definitely create some very cool and unique stuff. I would suggest, however, that you use this new creative power of animating variable fonts in whichever way you do it sparingly, as reflow could become an issue with jank.

I’m excited to see what people will create using variable fonts over the next year as it becomes more widely adopted. Not just limited to typographic treatments in layouts, but also in terms of animation and micro-interactions. It looks like a big undertaking for designers in creating such fonts, but credit to them. I’m sure we’ll be all the more appreciative. Thanks in advance type designers!

The post The New Good Ol’ Days appeared first on CSS-Tricks.

Growing Accessibility Conversations

Post pobrano z: Growing Accessibility Conversations

I started this year on a new path at Knowbility — to help people and organizations create accessible content and apps. But what was exciting and helped motivate me more were two things:

  1. WebAIM’s Accessibility Analysis of One Million Page Homepages. With over 97% of sites having WCAG failure of some kind, it’s a stinging indictment on our industry. There’s a lot of work to be done — that means outreach and education, helping other developers incorporate accessibility into their workflows, coding pull requests with accessibility fixes, making certain components for design systems are accessible and much more.
  2. The Supreme Court of the United States rejecting Domino’s appeal. The Supreme Court leaves the Ninth Circuit federal appeals court’s ruling, which means that people with disabilities who have trouble with sites or apps that are not accessible can bring claims under the Americans with Disabilities Act (ADA). This result means that organizations can’t kick the proverbial can down the road for making their apps and products accessible.

In regards to the One Million Pages, I noticed that web designers and developers outside of accessibility circles discussing accessibility with a sustained focus I hadn’t witnessed before.

And I would have conversations with people outside web design and development altogether about accessibility due to this court case alone. Companies and organizations were watching it very closely.

This added increased awareness from both WebAIM’s report and the Domino’s appeal has helped fuel the discussion, making it easier to keep the conversation going to make digital accessible to all.

The post Growing Accessibility Conversations appeared first on CSS-Tricks.