Paths

Rendering SVG Paths in WebGL

The following is a guest post by Matt DesLauriers. Matt combined a bunch of techniques and open source modules to build an incredible demo where simple recognizable icons explode into vector triangles only to reform into a completely new icon. Here he talks about some of the tools and ideas involved in making it happen, but beyond that, other approaches and other related concepts. This is some pretty advanced stuff and I hope it turns on some mental lightbulbs for people interested in this kind of mathematical, algorithmic web animation stuff. Take it away, Matt.

SVG is a great way to deliver resolution-independent icons, fonts, logos, and various other imagery. The format is widely supported, very flexible, and highly compact. For example, the new Google logo can be represented in as little as 146 bytes with SVG and a few tricks.

At the heart of the format is the <path> element, which provides a succinct means of describing a complex set path operations, like a glyph in a font set. Simply put, it describes a series of moveTo, lineTo, curveTo, and similar commands. For example, here’s the Google “G” in SVG:

<path d="M173 102a51 51 0 1 1-13-30m20 37h-53" stroke="#4a87ee"/>

See the Pen QjMrXV by Matt DesLauriers (@mattdesl) on CodePen.

However, in WebGL, rendering SVG paths is more challenging. The WebGL API, and by extension ThreeJS, is primarily built for rendering many triangles. It’s left up to the developer to implement tasks like complex shape rendering and text layout.

Here I’ll explore some of the approaches in handling the SVG <path> element in WebGL, and briefly discuss the tools involved in my latest demo for the svg-mesh-3d module. If you have a WebGL enabled browser, you can see the demo here.

Implementation

The code samples here will use ThreeJS to wrap the WebGL API, and browserify to bring together small npm modules of code. For more details on this approach, check out the Modules for Frontend JavaScript guide.

Development Tools

During the development of the ThreeJS demo, I used budo as the dev server, and babelify to transpile ES6. This leads to an easy and fast development cycle even with large bundles. brfs is used to inline shader files and our list of static SVG files to render. Instead of using Gulp or Grunt, the entire development and production is composed with two npm run tasks in a package.json file:

"scripts": {   "start": "budo demo/:bundle.js --live -- -t babelify -t brfs | garnish",   "build": "browserify demo/index.js -t babelify -t brfs | uglifyjs -cm > bundle.js" }

The gh-pages deploy is then automated with a single shell script.

For more details on npm run, see “How to Use npm as a Build Tool” by Keith Cirkel.

Approximation & Triangulation

For my svg-mesh-3d demo, I only needed to render a simple silhouette of the <path> data, with a solid color. Entypo icons is a good fit for this, and triangulation works well for these kind of SVGs.

The approach I took is to approximate the curves in a SVG path, triangulate its contours, and send the triangles to WebGL as a static geometry. This is an expensive step. It’s not something you would do every frame, but after the triangles are sitting on the GPU, you can use a vertex shader to animate them freely.

Most of this work has already been done through various modules on npm. The “glue” that brings them together is under 200 lines of code.

The final ThreeJS demo uses over 70 modules throughout its bundle, but heavily leans on some of the following under the hood:

In the end, the user experience becomes as simple as requiring the svg-mesh-3d module and manipulating the data it returns. The module is not specific to ThreeJS, and could be used with any render engine, such as Babylon.js, stackgl, Pixi.js, and even vanilla Canvas2D. Here is how it might be used in ThreeJS:

// our utility functions var createGeometry = require('three-simplicial-complex')(THREE); var svgMesh3d = require('svg-mesh-3d');  // our SVG <path> data var svgPath = 'M305.214,374.779c2.463,0,3.45,0.493...';  // triangulate to generic mesh data var meshData = svgMesh3d(svgPath);  // convert the mesh data to THREE.Geometry var geometry = createGeometry(meshData);  // wrap it in a mesh and material var material = new THREE.MeshBasicMaterial({   side: THREE.DoubleSide,   wireframe: true });  var mesh = new THREE.Mesh(geometry, material);  // add to scene scene.add(mesh);

Vertex Animation

When working with WebGL, it’s best to avoid uploading data to the GPU too often. This means we should try to build static geometry once, and then animate the geometry over many frames through vertex and fragment shaders.

To make our mesh “explode” into tiny pieces, we can change a uniform in a shader, which is a bit like a variable for GLSL. The vertex shader will be run on each vertex in our 3D geometry, allowing us to explode outward from the world origin at [ x=0, y=0, z=0 ].

To do this, we need a custom vertex attribute in our shader. For each triangle, its three vertices will use the same Vector3 to describe a direction. This direction is a random point on a sphere.

The bare-bones vertex shader below just transforms each vertex position.xyz by its direction.xyz, scaled by the animation factor. The position.xyz is in model-space, in the range -1.0 to 1.0.

attribute vec3 direction; uniform float animation;  void main() {   // transform model-space position by explosion amount   vec3 tPos = position.xyz + direction.xyz * animation;    // final position   gl_Position = projectionMatrix * modelViewMatrix * vec4(tPos, 1.0); }

When the uniform is at 0.0, the triangles will be at their initial location, and the logo will be a solid fill:

When the uniform is set to 1.0, the triangles will be pushed out along the direction vector, away from world center, and form an “explosion” of sorts:

This looks good, but there are two more features for some polishing touches. The first is to scale the triangle toward its centroid. This is used to animate the triangles in and out independently of the explosion. For this, we need to submit another custom vertex attribute, like we did with direction.

The second is to add a bit of spin and chaos to the explosion by transforming the direction.xyz vector by a rotation matrix. The angle for the rotation matrix is determined by the animation factor, as well as the sign() of the triangle centroid’s x position. This means that some triangles will rotate in the opposite direction.

With the final vertex shader in place, our application can now animate thousands of triangles at a silky smooth 60 FPS. Below is a screen shot with wireframe rendering, and the randomization parameter set to 1500.

Other Approaches

I chose cdt2d since it is numerically robust, can handle arbitrary input with holes, and is engine-agnostic. Other triangulators, such as earcut, tess2, and ThreeJS’s own triangulator would have also been good options.

Aside from triangulation, it is worth mentioning some other approaches to SVG <path> rendering in WebGL. Curve approximation and triangulation has its downsides: it is slow to build the geometry, it is difficult to do correctly, it increases the size of the final code bundle, and it shows “stepping” or jagged edges when you zoom in.

Rasterization

A simple and effective approach is to rasterize SVG data to a HTMLImageElement, and then upload that to a WebGL Texture. This not only supports the <path> element, but the full range of SVG features covered by the browser, such as filters, patterns, text and even HTML/CSS in Firefox and Chrome.

For example, see svg-to-image for how this can be done with Blob and URL.createObjectURL. In ThreeJS and browserify, the code might look like this:

// our rasterizing function var svgToImage = require('svg-to-image');  // create a box with a dummy texture var texture = new THREE.Texture(); var geo = new THREE.BoxGeometry(1, 1, 1); var mat = new THREE.MeshBasicMaterial({   map: texture });  // add it to the scene scene.add(new THREE.Mesh(geo, mat));  // convert SVG data into an Image svgToImage(getSvgData(), {   crossOrigin: 'Anonymous' }, function (err, image) {   if (err) {     // there was a problem rendering the SVG data     throw new Error('could rasterize SVG: ' + err.message);   }    // no error; update the WebGL texture   texture.image = image;   texture.needsUpdate = true; });  function getSvgData () {   // make sure that "width" and "height" are specified!   return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="1024px" height="1024px">.....</svg>'; }

Result:

However, since we are rasterizing the graphic into pixels, it is no longer scalable and will produce aliasing when we zoom in. Often, you will need large images to produce smooth edges, which will put a strain on texture memory.

This does not produce triangles, so to mimic the “explosion” we could use simple Delaunay triangulation, which is much faster and easier than constrained Delaunay triangulation.

Stencil Buffer

An old trick is to use the Stencil Buffer to render complex polygons with holes. This is much faster than the triangulation step involved in my demo, but has a major drawback: it lacks MSAA (anti-aliasing) in most browsers. Some engines might use this in conjunction with other techniques, such as FXAA (anti-aliasing in a post-process), or adding an outline to the shape using prefiltered lines.

Pixi.js uses this for complex shape rendering, see a demo here.


A complex vector illustration rendered with the stencil buffer

You can read more about it here:

Loop-Blinn Curve Rendering

A more advanced approach for hardware path rendering is described in Resolution Independent Curve Rendering by Loop and Blinn. For a more approachable introduction, check out “Curvy Blues” by Michael Dominic.

This technique produces the best curve and path rendering. It is fast, infinitely scalable, and can leverage the GPU for anti-alasing and special effects. However, it is much more complex to implement. There are many edge cases that might appear in arbitrary SVG paths, such as overlapping or self-intersecting curves. Furthermore, it is patented, so not a great choice for an open source project.

Further Reading

This post only scratches the surface of scalable vector graphics in WebGL. The SVG format goes far beyond filled paths, and includes features like gradients, strokes, text, and filters. Each of these introduces new complexities in the scope of WebGL, and rendering the format entirely on the GPU would be a tremendous mountain of work.

For more reading on some of these topics, check out the following:


Rendering SVG Paths in WebGL is a post from CSS-Tricks

CSS-Tricks

Building A Circular Navigation with CSS Clip Paths

The following is guest post by Sara Soueidan. Sara is always doing wonderful creative work, and then does an equally wonderful job explaining all the ins and outs of how it was done with web tech. Here, she’ll walk us through building a circular menu in what (should be) the simplest possible way.

The CSS clip-path property is one of the most underused and yet most interesting properties in CSS. It can be used in conjunction with CSS Shapes to create interesting layouts, and can be taken to the extreme to create some incredibly impressive layouts and animations like the Species in Pieces project.

While exploring the creation of arbitrarily-shaped UI components using CSS and SVG, it occurred to me that the clip-path property, when combined with SVG paths, can be used to create circular menus fairly easily, especially considering the (expected) browser behaviour when handling pointer events on clipped regions as per the specification. Let’s explore this idea a little further.

Some Background

A couple of years ago, I wrote an article for Codrops about using CSS transforms and some CSS trickery to create CSS-only circular menus. That technique is anything but optimal or intuitive, and requires a lot of workarounds and hacks to achieve the desired effect.

To create a menu using that technique, you fake a sector shape by skewing the menu items, and then clipping them, so to speak, by hiding the overflow on their container. And since you start out by skewing the menu items, you need to follow up by “unskewing” the content inside of them which would be distorted after their container has been skewed.

The resulting menu is not flexible, requires a lot of hacking, and the content inside of it is limited to only icons in most cases because other content would be hard to position and style inside of the skewed items. You can learn all the details of that technique in the article.

Today, the CSS clip-path property—combined with SVG paths—can also be used to create circular menus in CSS. The technique is not hacky, does not require any weird transformation workarounds, and works just as you’d expect. There are a couple of limitations and a browser bug at the time of writing of this article (see following section), but the code required to create the menu is surprisingly short, clean and easy to understand.

But before we dig into the code, and even though the code is straightforward and easy to follow, you might want to learn a little more about clipping paths first, if you’re not already familiar with what they are, what they do, and how they work. You can learn all about them in an article on my blog.

We also need to go over some notes regarding browser support before we get into the code and to the live demos:

Browser Support (and Bugs)

  • We’ll be using the CSS clip-path property, so first thing to note is the browser support. As the support table from CanIUse shows below, the property’s support is not at its best, especially with no version of IE supporting it, not even MS Edge.
  • IE does not support clip paths in CSS yet, but is it currently “under consideration”.
  • If you use a CSS basic shape function to define a clipping path, Firefox will not apply that clip path because it currently only supports clip path values that reference an SVG <clipPath> element. Firefox does not support the basic CSS shape functions yet. (See end of this section for a fallback tip.)
  • WebKit/Blink-based browsers don’t handle pointer events correctly when the clip path used is defined in SVG. This is a bug. By default, pointer events should not be dispatched outside the visible area of the clipped element; that is the expected behaviour as per the specification. When you use a CSS basic shape function to define the clip path, these browsers handle pointer events correctly. However, when you apply an SVG clipPath via the clip-path property, pointer events are still dispatched outside the visible area, which will interfere with and block pointer events on any element lying behind/underneath the clipped element. I filed a bug report about this while working on this article. Let’s hope it gets fixed soon. This bug means that the demo in this article is, for now, not working in WebKit/Blink-based browsers. (Sorry.)
  • There is also another bug in Blink-based browsers that causes an extremely weird rendering problem (it’s a compositing issue) which also makes applying transformation effects to a clipped elements on hover practically impossible today. So, for the menu in our article, scaling the menu up and down on click, for example, using CSS scale transformations causes a huge problem, so we will be skipping the opening/closing effect. I also filed a bug report for this issue.

In short: the demo for this article currently only works as expected in Firefox at the time of publication.

Should you decide to change the clipping path used in the article and replace it with a CSS basic shape, the demo will work in other browsers (excluding IE/Edge), but not in Firefox.

If you do want to use a basic CSS shape function to define a clip path and want it to work in Firefox, you can always create the same shape using an SVG <clipPath> element and apply it as a fallback for Firefox.

For example:

.element {   clip-path: url(#SVGPolygonShape); /* For Firefox */   clip-path: polygon(...); /* For other browsers */ }

The last thing to note here is that only Firefox currently supports referencing a clipPath element defined in an external SVG; all other browsers require the defined SVG path to be inlined in the document. There is thread to track this issue on the Chromium Project.

Now that browser bugs and support are clear, let’s dig into code. We’ll start with the markup for the menu.

Marking It Up

The markup is pretty straightforward: the menu is an unordered list of items wrapped in links and containing some sort of content like text or an icon. I am using a simple “icon” label for demonstration purposes.

<ul class="menu">   <li class="one">     <a href="#">       <span class="icon">icon-1</span>     </a>   </li>   <li class="two">     <a href="#">       <span class="icon">icon-2</span>     </a>   </li>   <li class="three">     <a href="#">       <span class="icon">icon-3</span>     </a>   </li>   <li class="four">     <a href="#">       <span class="icon">icon-4</span>     </a>   </li>   <li class="five">     <a href="#">       <span class="icon">icon-5</span>     </a>   </li>   <li class="six">     <a href="#">       <span class="icon">icon-6</span>     </a>   </li> </ul>

That’s all the markup we need.

Defining the Clip Path (The Sector Shape)

To create a sector shape, we need to be able to draw an arc from one point to the other in the middle of a path, and that’s currently not possible in CSS using the CSS basic shape functions: circle(), ellipse(), inset() or polygon(). (That is, unless you want to use the polygon() function with a very large number of points positioned so close to each other that they appear as an arc. But who would want to do that?)

So, we need a more straightforward way to draw the sector shape. And fortunately, the clip-path property grants us the ability to do that by accepting a reference to an SVG path as a value for the clipping path.

In other words, you can define the clipping path shape you want (it can even be made up of multiple separated paths) in SVG, wrap that shape in an SVG <clipPath> element with an ID, and then reference that path in CSS using the URL syntax:

clip-path: url(#clipPathID);

Thus, defining our sector shape in SVG becomes very simple. However, there is some math and positioning considerations to be taken into account.

First, you need to determine the number of items you want the menu to have; that will determine the value of the central angle for the sectors.

Next, you need to draw the sector shape using an SVG <path> element. The path commands available in SVG enable you to draw the path following some simple drawing rules.

We’ll be using four path commands to draw the sector shape: M, l (small L), A and z. But before we start drawing, we need to determine where exactly the sector will clip the items, and for that, we need to plan the concept out before we get to execution.

Determining the position of the clip path on the menu items

The menu items will need to be positioned absolutely on top of each other and then clipped to sector shapes that will all form the overall circular shape of the menu.

In order to do that, we need to think of these items as a set of layers (which they literally are), and then cut off parts of these layers such that, in the end, only the sector shapes of these layers will be visible. The following image shows the layers for four of the six items of the menu we are creating:

The translucent black boxes are there only for demonstration purposes. What’s really happening is the items are rotated, not the clip paths. That is, for each item (rectangular box), the item is clipped to the exact same sector shape (such that all items are identical), then each item is rotated by the necessary angle so that their clipped regions do not overlap anymore, and form the circular menu instead. The following image demonstrates that more clearly:

If you look at the second sector in the above image (try to tilt your head a bit so that the black border is on top) (Yes, I tilted my head as I worked on these illustrations), you will be able to see the sector position inside of the item more clearly: It is the exact same position as the sector in the first item, except that the first item was not rotated after it had been clipped.

Note that in the second image above, the black boxes indeed represent the list items as they would be positioned on the page, because the clip path will affect the visible/painted area of the element, but it is practically still a rectangle, even if only a small part of it is showing through its new non-rectangular viewport.

Before we move forward, here is an animated version showing how the items would be positioned, clipped and rotated to achieve the overall menu using the clipped elements. The aim of this GIF is to show that they are all clipped identically before they are rotated.

Even though the elements are clipped, they are still rectangular in principle. So when you visualise their rotation, remember that it’s a rectangular element being rotated, but only the non-rectangular shape inside of it is visible.

Determining the point coordinates of the clip path when applied on the menu items

Now that we know how the items will be rotated, we know that the initial sector shape will be identical for all the items. So all we need next is to draw the sector shape in SVG. And to do that, we need to see how the coordinates of the points defining the sector will be determined.

Let’s take a look at the code for the sector shape before we dissect it; the following <svg> goes into the page so that the clip path can be referenced in the CSS:

<svg height="0" width="0">   <defs>     <clipPath clipPathUnits="objectBoundingBox" id="sector">       <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M0.5,0.5 l0.5,0 A0.5,0.5 0 0,0 0.75,.066987298 z"></path>     </clipPath>   </defs> </svg>

The most important part in the above code is the clipPathUnits="objectBoundingBox" declaration. The clipPathUnits attribute accepts two values: userSpaceOnUse and objectBoundingBox. The former will draw the path using the entire page’s coordinate system, and the latter will use the item’s coordinate system—which is what we want.

When you use objectBoundingBox, the coordinates of the points used to draw the sector path are set using relative values in the range [0, 1]. These values are very similar in principle to percentage values, and will be calculated relative to the element’s bounding box; i.e. the element’s width and height, in our case. That was is why the values in the above snippet are all less than 1.

If you use the userSpaceOnUse value instead of objectBoundingBox, the browser will use the coordinate system on the page (in the case of HTML) or the current user coordinate system in use (that of the canvas, in the case of SVG) when positioning your clip path, meaning that it may or may not be really applied to your element, depending on where the element is on the page or canvas. You can read a little more about this in my article on the subject.

Knowing how the sector will be positioned inside the menu item (square) from the previous section, we can illustrate the position and relative point coordinates:

These coordinates, along with the relative circle radius for the sector, will be used by the path commands to draw the sector shape.

Given how we want it to be positioned on our menu items, the coordinates of the points defining the clip path can be determined as shown in the image. Three points are required to draw the sector shape, and we also need the radius of the circle for that sector.

The radius of the circle is 0.5, which is half the width of the element. The center of that circle is also on (0.5, 0.5). The second point on the sector making up the base is located at (1, 0.5). The third point coordinates, by way of some simple math, is located at (0.75, 0.066987298). Then, using SVG arc (A) and line commands (l), the path is drawn. We won’t get into the details of how it is drawn because it’s outside the scope of this tutorial, but here is an interactive demo showing this drawing in action. Click on the button to play it.

See the Pen Building A Circular Menu With SVG — #1 by Sara Soueidan (@SaraSoueidan) on CodePen.

To learn how the path commands are used to draw the sector shape, refer to this article on my blog.

With this data at hand, the <path> defining our <clipPath> can be created, and is ready to be applied to our menu items.

Clipping The Menu Items

With the SVG clip path ready, we can now clip the menu items using the clip-path property.

First, the items are positioned absolutely on top of each other inside the menu. We start by creating a positioning context:

.menu {   position: relative;    list-style: none;   margin: 30px auto;    /* padding trick for maintaining aspect ratio */   height: 0;   padding: 0;   padding-top: 70%;   width: 70%; }

To make sure the menu has square dimensions, I’m using the padding hack to make sure it maintains a 1:1 aspect ratio. Since we want it to be fluid, I’m using percentage values for the width. Using media queries, you can specify the minimum and maximum dimensions you want, and adjust the padding (for the hack) accordingly. The live demo below includes that part, so you can play with these values and adjust them to your liking.

Next, position the items inside the menu and clip them to the sector shape:

.menu li {    position: absolute;   top: 0;   left: 0;   width: 100%;   height: 100%;    clip-path: url(#sector) }  .menu li a {   display: block;   width: 100%;   height: 100%; }  .menu li:hover {   background-color: gold; }

This will clip all items to the sector shape, and will only result in one item being shown (the last one, on top of all the others), with the rest “behind” it. The remaining items are not visible because they are all clipped in the same area and are thus covered by the last one.

To show the rest of the items and form our circular menu, we need to rotate the items by the necessary angles. Item #1, which has index 0, will be rotated by 0 * angle = 0 * 60 = 0deg.

Item #2 will be rotated by 1 * 60 = 60deg; item #3 will be rotated by 2 * 60 = 120deg. And so on.

.one {   background-color: $  base;   transform: rotate(0deg); } .two {   background-color: darken($  base, 7%);   transform: rotate(-60deg); } .three {   background-color: darken($  base, 14%);   transform: rotate(-120deg); } .four {   background-color: darken($  base, 21%);   transform: rotate(-180deg); } .five {   background-color: darken($  base, 28%);   transform: rotate(-240deg); } .six {   background-color: darken($  base, 35%);   transform: rotate(-300deg); }

We could have used a Sass loop to automate this, but let’s lets not get too far away from our focus.

And that’s all it takes to get the circular menu items using CSS clip paths. Here is the live demo:

See the Pen Circular Menu using CSS clip-path by Sara Soueidan (@SaraSoueidan) on CodePen.

Note that, since Chrome has that bug with pointer events at the time of writing of this article, you can replace the SVG clip path reference with this:

clip-path: polygon(50% 50%, 100% 50%, 75% 6.6%);

to get an idea of how the menu works with proper pointer events. Hover over the items to see their background color change. Remember, the new clip path will not work in Firefox, though. So, for the time being, you can add the above line after the clip-path URL syntax, so that Firefox can use the latter.

This is how the menu will look like with the polygon() clip path applied:

I’ve added this line of CSS to the above menu so you can uncomment it if you want.

Adding Icons/Content To The Menu Items

… is simple. All you need to do is make sure the icons/text/whatever are visible inside the visible area of the element (the sector shape). Again, using the coodrdinate system established by the element’s height and width, you can estimate the position of the items so that they show up inside the new sector-shaped viewport.

It might take some experimenting to get the exact alignment that you want, depending on the content of the items.

For our demo, I am using text, so positioning that text at 30% down the element and 15% to the left of the right edge was a good place to start.

After we’ve specified the position for the text in our example, we need to rotate the text by the same angle as the sector to make sure it looks as it should inside the menu, otherwise you would end up with this:

instead of this:

And the styles for the text inside the items are:

.icon {   position: absolute;   /* exact values here depend on what you are placing inside the items (icon, image, text, etc.) */   right: 15%;   top: 30%;   /* angle of rotation = angle of the sector itself */   transform: rotate(60deg);    /* style further as needed */ }

Another Way

The above technique is the simplest way to achieve this circular menu using CSS clip paths.

Instead of using the same sector shape to clip all elements and then rotate those elements in CSS, you could have avoided rotating the elements and used a rotated sector shape instead, so that each menu item will be clipped such that the resulting sectors are already rotated to form the circular shape.

However, that technique is more complicated because you need to specify the point coordinates as well as rotation for every SVG <path>, not to mention it would require more work to get the position for the content of each item to show through each of those sectors. It is more complicated and time-consuming, so definitely not better than the above technique.

That said, I still want to show you what the code for the clip paths in this case would look like:

<svg height="0" width="0">   <defs>     <clipPath clipPathUnits="userSpaceOnUse" transform="matrix(1,0,0,1,0,0)" id="one-2">       <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>     </clipPath>     <clipPath clipPathUnits="userSpaceOnUse" transform="matrix(0.5,-0.86602,0.86602,0.5,-91.5063509461097,341.5063509461096)" id="two-2">       <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>     </clipPath>     <clipPath clipPathUnits="userSpaceOnUse" transform="matrix(-0.49999,-0.86602,0.86602,-0.49999,158.49364905389024,591.5063509461097)" id="three-2">       <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>     </clipPath>     <clipPath clipPathUnits="userSpaceOnUse" transform="matrix(-1,0,0,-1,500,500)" id="four-2">       <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>     </clipPath>     <clipPath clipPathUnits="userSpaceOnUse" transform="matrix(-0.5,0.86602,-0.86602,-0.5,591.5063509461097,158.4936490538905)" id="five-2">       <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>     </clipPath>     <clipPath clipPathUnits="userSpaceOnUse" transform="matrix(0.5,0.86602,-0.86602,0.5,341.5063509461096,-91.5063509461097)" id="six-2">       <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>     </clipPath>   </defs> </svg>

That’s quite a lot of SVG code compared to the couple of lines from the previous sections.

The above snippet contains a clip path for each sector, rotated as necessary, and without the coordinates converted into relative ones in the range [0, 1]; you would need to convert those coordinates as well. Each of the above sectors can then be used as a clip path for its respective element in the menu. Not very optimal this one, is it?

Note that the above code is generated from a circular menu generator I created a while back, so, no, I did not code this by hand. (More about it in the next section.)

SVG Circular Menus

The clip-path technique is a great one and, conceptually, it works pretty well; but the current browser bugs make the resulting menu practically unusable today.

A few months ago I explored the idea of using SVG and SVG paths to create circular menus, considering how well-equipped SVG is for creating non-rectangular shapes in general. Using SVG paths, the sector shapes would be drawn and wrapped in a link <a>. Creating the sectors is straightforward and requires the same considerations and steps as we did earlier, except that you can stick to absolute values for the point coordinates instead of relative values, because the entire SVG canvas will be the coordinate system where the sectors are drawn. You don’t need to apply the sector shapes to any element—the shape paths themselves will be the menu items.

Thus, if you do need a circular menu to use in your UIs today, then SVG is currently your best option. Support is great (IE9+ and all modern browsers), and it works just as you’d expect. Plus, you’d get the flexibility of SVG, especially that you get to use SVG icons inside the menu instead of icon fonts. Should you be interested, you can check out the article explaining how to do that here.

Since it takes quite some time to create a circular menu in SVG, I’ve also created a generator that enables you to customise the defining characteristics of the menu visually, and then generates the code for that menu for you, ready to be embedded. The tool also includes an extensive guide about the generated code, how to embed it, how to animate the menu opening/closing, and even some UX considerations for when you want to use circular menus.

Circulus: the SVG ciruclar menu generator.

You can find the generator here.

Final Words

Don’t use the old CSS transforms technique for creating circular menus. Clip paths in CSS are pretty awesome and very powerful, but until support gets better, they’re not always a go-go. SVG is also just as awesome, and is currently better suited for creating circular menus, at least until CSS clip path support gets better.

I hope you liked this article and found it useful. Thanks for reading!


Building A Circular Navigation with CSS Clip Paths is a post from CSS-Tricks

CSS-Tricks

Dropdown Menus with More Forgiving Mouse Movement Paths

This is a fairly old UX concept that I haven’t heard talked about in a while, but is still relevant in the case of multi-level dropdown menus. A fine-grained pointer like a mouse sometimes has to travel through pretty narrow corridors to accurately get where it needs to in a dropdown menu. It’s easy to screw up (have the mouse pointer leave the path) and be penalized by having it close up on you. Perhaps we can make that less frustrating.

You can get to the menu item you want, but there are some narrow passages along the way.
Many dropdowns are designed such that when the right area isn’t in :hover, or a mouseleave or mouseout occurs, the submenu where the desired menu item is may close on you.

A Basic CSS Approach

Dropdown menus are typically designed such that a submenu is revealed through CSS on a :hover of a parent element. Or on a mouseenter / mouseover in JavaScript and a state change. So the trick is to try and prevent any of those events from happening too easily. Widen up the corridors, as it were.

The whole reason I got to thinking about this is because the corridors were mighty narrow for our submenus in the main dropdown at CodePen. To widen them up at the choke point, I added a couple of pseudo elements to the submenu. If the mouse goes over those, nothing happens, it’s just like the mouse is within the submenu as normal.

The red there is just to demonstrate where those pseudo elements are positioned.

This is a super lightweight technique requiring only CSS, which I like. The slight danger is that you can cover up a decent amount of adjacent menu items, which could mean a user hovering over a legit menu item and not being able to click it. I imagine in a lot of cases they just move the mouse a little and it works, but it’s definitely a balancing act between too small and too big.

The history of this goes back to 2005 and a classic Position is Everything demo:

And potentially a Thierry Koblentz demo.

Widening CSS Boundaries

John Gardner created an approach where all the menus have extra space around them which provide the forgiving hover-offs:

See the Pen CSS3 Dropdown Menu by John Gardner (@Alhadis) on CodePen.

(Un)hover intent

Have you heard of “hover intent”? It’s this concept where you don’t trigger a response right away to mouse location events. You wait a split second, that way you can be sure the user wanted a reaction. It’s like intentional lag. It’s used in situations where the UI might be too twitchy without it.

The opposite of that we could call unhover intent. Like we react to a hover event immediately, but don’t react to a hover off for a little bit. In CSS, you could do something like:

.submenu {   visibility: hidden;   transition: 0.2s 1s; /* delay of 1 seconds on hover off */ } .parent:hover .submenu {   visibility: visible;   transition-delay: 0s; /* react immediately on hover */ }

Note we are using visibility here, which is transitionable, rather than display, which is not (weirdly enough).

See the Pen Unhover Intent in CSS by CSS-Tricks (@css-tricks) on CodePen.

JavaScript is certainly capable of unhover intent as well. You’d approach it kind of like a debounce, where you wouldn’t call the functions that do the closing unless some time has passed. That could be done with a setTimeout that you clear if the mouse comes back on. This is overly simplistic, but:

var timer;  $  (".parent").on("mouseover", function() {   clearTimeout(timer);   openSubmenu(); }).on("mouseleave", function() {   timer = setTimeout(     closeSubmenu   , 1000); });  function openSubmenu() {   $  (".submenu").addClass("open"); } function closeSubmenu() {   $  (".submenu").removeClass("open"); }

See the Pen Unhover Intent in JS by CSS-Tricks (@css-tricks) on CodePen.

Neither of these unhover demos dealt with the corridor problem by expanding the size, they dealt with it by forgiving a quick miss in mouse location.

Avoid the narrow corridor

There is no rule that submenu dropdowns need to go out to the side. If they open up directly below, the corridor remains wide. Here’s an example of that by Timothy M. LeBlanc:

See the Pen Dropdown Menus by White Wolf Wizard (@WhiteWolfWizard) on CodePen.

Get fancy with a JavaScript-built triangle

There is a great post from a few years ago by Ben Kames on this subject. He was checking out how snappy the Amazon.com dropdown menus were, compared to other dropdowns that use delay tactics to handle the usability. The magic turned out to be math! If the mouse traveled at a reasonable angle toward the newly-opened submenu, it wouldn’t trigger the other menu items. Visual aid:

Ben turned it into a jQuery plugin that you can demo.

He’s also very right about this:

I’m sure this problem was solved years and years ago, forgotten, rediscovered, solved again, forgotten, rediscovered, solved again.

That’s a sad truth about lots of things in our industry.

Jon Neal took a stab at removing the jQuery dependency and improving the math with “barycentric coordinates”:

See the Pen Aim-Aware Menus by Jonathan Neal (@jonneal) on CodePen.

Other Concerns

I think a wise person once said:

“Sometimes visitors to your website will have a mouse and sometimes they won’t.”

So, hopefully your dropdowns work with touch as well. You certainly can make them work! You can also make the call if you want to bundle all code together for efficiency, or if some kind of feature detection is in order to load different scripts and styles as needed (or a combo of both).

Keyboard accessibility is another classic concern with dropdown menus. I just didn’t want to leave you with the idea that helping mouse movement is all you need to do for good dropdown UX. There is always something to improve!


Dropdown Menus with More Forgiving Mouse Movement Paths is a post from CSS-Tricks

CSS-Tricks