Archive

Archive for March, 2019

SVG Circle Decomposition To Paths

March 22nd, 2019 No comments

SVG Circle Decomposition To Paths

SVG Circle Decomposition To Paths

Bryan Rasmussen

2019-03-22T13:00:08+01:002019-03-23T08:59:42+00:00

This article starts with a confession: I like to hand-code SVG. It’s not always the case but often enough it could seem peculiar to people who do not share my predilection. There are a good number of benefits in being able to write SVG by hand, such as optimizing SVGs in ways a tool can’t (turning a path into a simpler path or shape), or by simply understanding how libraries like D3 or Greensock work.

With that said, I’d like to look more closely at circular shapes in SVG and things we can do with them when we move past a basic circle. Why circles? Well, I love circles. They’re my favorite shape.

First off (hopefully you’ve seen a basic circle in SVG before), here’s a pen that shows one:

See the Pen circle by Bryan Rasmussen.

A lot of things can be done with a circle: it can be animated and it can have different colors applied to it. Still, there are two very nice things that you cannot have a circle do in SVG 1.1: You cannot make another graphical element move along the circle’s path (using the animateMotion element) and you cannot have shape a text along a circle’s path (this will only be allowed after SVG 2.0 is released).

Turning Our Circle Into A Path

There is a little online tool that can help you create paths out of circles (you can try it out here), but we’re going to do be creating everything from scratch so we can find out what’s really going on behind the scenes.

To make a circular path, we’re going to actually make two arcs, i.e. semicircles that complete the circle in one path. As you’ve probably noticed in the SVG above, the attributes CX, CY, and R respectively define where the circle is drawn along the X and Y axis, while R defines the radius of the circle. The CX and CY create the center of the circle, so the circle is drawn around that point.

Replicating that circle could look like this:

<path
    d="
      M (CX - R), CY
      a R,R 0 1,0 (R * 2),0
      a R,R 0 1,0 -(R * 2),0
    "
/>

Note that CX is the same as the cx attribute of the circle; the same goes for CY and the cy attribute of the circle, as well as R and the r attribute of the circle. The small a character is used to define a segment of an elliptical arc. You can use an optional Z (or z) to close the path.

The lowercase letter a denotes the beginning of an elliptical arc drawn relatively to the current position — or in our specific case:

<path
  d="
    M 25, 50
    a 25,25 0 1,1 50,0
    a 25,25 0 1,1 -50,0
  "
/>

You can see the magic happening in this pen:

See the Pen circle from path by Bryan Rasmussen.

Hidden underneath the path is a circle with a red fill. As you play around with values of the path, you’ll see that circle as long as the path totally covers the circle (the path itself is a circle of the same size), and we’ll know that we’re doing things right.

One thing you should also know is that as long as you are drawing relative arcs, you don’t need to repeat the a command for each arc you draw. When your first 7 inputs are done for your arc, the second 7 inputs will be taken for the next arc.

You can try this out with the pen above by removing the second a in the path:

a 25,25 0 1,1 50,0

25,25 0 1,1 -50,0

This may look the same, but I prefer to leave it in until I am ready to finish a drawing, and this also helps me to keep track of where I am.

How This Path Works

First, we move to an absolutely positioned X,Y coordinate in the image. It does not draw anything there — it just moves there. Remember that for a circle element CX, CY denotes the center of the circle; but as it happens in the elliptical arc, the true CX and CY of the arc will be calculated from the other properties of that arc.

In other words, if we want our CX to be at 50 and our radius is 25, then we need to move to 50 - 25 (if we are drawing from left to right, of course). This means that our first arc is drawn from 25 X, 50 Y which results to our first arc being 25,25 0 1,0 50,0.

Let’s break down what the value 25,25 0 1,0 50,0 of our arc actually means:

  • 25: The relative X radius of the arc;
  • 25: The relative Y radius of the arc;
  • 0 1,0: I’m not going to talk about the three middle values (rotation, large-arc-flag, and the sweep-flag properties) because they are not very important in the context of the current example as long as they are the same for both arcs;
  • 50: The ending X coordinate (relative) of the arc;
  • 0: The ending Y coordinate (relative) of the arc.

The second arc is a 25,25 0 1,0 -50,0. Keep in mind that this arc will start drawing from wherever the last arc stopped drawing. Of course, the X and Y radius are the same (25), but the ending X coordinate is -50 of where the current one is.

Obviously this circle could have been drawn in many different ways. This process of turning a circle into a path is known as decomposition. In the SVG 2 spec decomposition of a circle will be done with 4 arcs, however, the method it recommends is not possible to use yet, as it currently depends on a feature named segment-completing close path which has not yet been specified.

In order to show you that we can draw the circle in a lot of ways, I have prepared a little pen with various examples:

See the Pen all circles by Bryan Rasmussen.

If you take a closer look, you’ll see our original circle along with five different examples of how to draw paths on top of that circle. Each path has a child desc element describing the use of CX, CY and R values to build the circle. The first example is the one we discussed here while three others use variations that should be comprehensible from reading the code; the last examples uses four semicircular arcs instead of two, replicating somewhat the process described in the SVG 2 spec linked above.

The circles are layered on top of each other using SVG’s natural z-indexing of placing elements that come later in the markup on top of the ones that come earlier.

If you click on the circular paths in the pen, the first click will print out how the path is structured to the console and add a class to the element so that you will see the stroke color of how the circle is drawn (you can see that the first circle is drawn with a starting wedge from the stroke). The second click will remove the circle so you have the ability to interact with the circle below.

Each circle has a different fill color; the actual circle element is yellow and will say “You clicked on the circle” to the console whenever it is clicked on. You can also, of course, simply read the code as the desc elements are quite straightforward.

Going From A Path To A Circle

I suppose you’ve noticed that while there are many different ways to draw the circle, the paths used still look pretty similar. Often — especially in SVGs output from a drawing program — circles will be represented by paths. This is probably due to optimization of the graphics program code; once you have the code to draw a path you can draw anything, so just use that. This can lead to somewhat bloated SVGs that are hard to reason about.

Recommended reading: “Tips For Creating And Exporting Better SVGs For The Web” by Sara Soueidan

Let’s take the following SVG from Wikipedia as an example. When you look at the code for that file, you will see that it has a lot of editor cruft once you’ve run it through Jake Archibald’s SVGOMG! (which you can read more about here). You’ll end up with something like the following file which has been pretty optimized, but the circles in the document are still rendered as paths:

See the Pen Wikipedia Screw Head Clutch Type A by Bryan Rasmussen.

So, let’s see if we can figure out what those circles should be if they were actual circle elements given what we know about how paths work. The first path in the document is obviously not a circle while the next two are (showing just the d attribute):

M39 20a19 19 0 1 1-38 0 19 19 0 1 1 38 0z
M25 20a5 5 0 1 1-10 0 5 5 0 1 1 10 0z

So remembering that the second a can be left out, let’s rewrite these to make a little more sense. (The first path is the big circle.)

M39 20
a19 19 0 1 1-38 0
a19 19 0 1 1 38 0z

Those arcs are then obviously the following:

aR R 0 1 1 - (R * 2) 0
aR R 0 1 1 (R * 2) 0

This means that our circle radius is 19, but what are our CX and CY values? I think our M39 is actually CX + R, which means that CX is 20 and CY is 20 too.

Let’s say you add in a circle after all the paths like this:

<circle
 fill="none"
 stroke-width="1.99975"
 stroke="red"
 r="19"
 cx="20"
 cy="20"
/>

You will see that is correct, and that the red stroked circle covers exactly the large circle. The second circle path reformulated looks like this:

M25 20
a5 5 0 1 1-10 0 
5 5 0 1 1 10 0z

Obviously, the radius is 5, and I bet our CX and CY values are the same as before: - 20.

Note: If CX = 20, then CX + R = 25. The circle is sitting inside the bigger one at the center, so obviously it should have the same CX and CY values.

Add the following circle at the end of the paths:

<circle
 fill="yellow"
 r="5"
 cx="20"
 cy="20"
/>

You can now see that this is correct by taking a look at the following pen:

See the Pen Wikipedia Screw Head Clutch Type A_ with example circles by Bryan Rasmussen.

Now that we know what the circles should be, we can remove those unneeded paths and actually create the circles — as you can see here:

See the Pen Wikipedia Screw Head Clutch Type A optimized by Bryan Rasmussen.

Using Our Circular Path For Wrapping Text

So now that we have our circles in paths, we can wrap text on those paths. Below is a pen with the same paths as our previous “All Circles” pen, but with text wrapped on the path. Whenever you click on a path, that path will be deleted and the text will be wrapped on the next available path, like so:

See the Pen all circles wrapped Text by Bryan Rasmussen.

Looking at the different paths, you’ll see tiny differences between each one (more on that in a bit), but first there is a little cross-browser incompatibility to be seen — especially noticeable in the first path:

Firefox Developer
Chrome
Microsoft Edge

The reason why the starting “S” of “Smashing” is sitting at that funny angle in the Firefox solution is that it is where we actually started drawing our path at (due to the v-R command we used). This is more obvious in the Chrome version where you can clearly see the first pie-shaped wedge of our circle that we drew:

Chrome does not follow all the wedges, so this is the result when you change the text to be “Smashing Magazine”.

The reason is that Chrome has a bug regarding inheritance of the textLength attribute declared on the parent text element. If you want them both to look the same, put the textLength attribute on the textPath element as well as the text. Why? Because it turns out that Firefox Developer has the same bug if the textLength attribute is not specified on the text element (this has been the case for some years now).

Microsoft Edge has a totally different bug; it can’t handle whitespace in between the Text and the child TextPath element. Once you have removed whitespace, and put the textLength attribute on both the text and textPath elements, they will all look relatively the same (with small variations due to differences in default fonts and so forth). So, three different bugs on three different browsers — this is why people often prefer to work with libraries!

The following pen shows how the problems can be fixed:

See the Pen all circles wrapped Text fixed TextLength by Bryan Rasmussen.

I’ve also removed the various fill colors because it makes it easier to see the text wrapping. Removing the fill colors means that my little function to allow you to cycle through the paths and see how they look won’t work unless I add a pointer-events="all" attribute, so I’ve added those as well.

Note: You can read more about the reasons for that in “Managing SVG Interaction With The Pointer Events Property” explained by Tiffany B. Brown.

We’ve already discussed the wrapping of the multiarc path, so let’s now look at the others. Since we have one path we are wrapping on, the text will always move in the same direction.

Image Path Explanation
M CX, CY
a R, R 0 1,0 -(R * 2), 0
a R, R 0 1,0 R * 2, 0
and uses the translate function to move +R on the X axis.
The starting position for our textPath (since we have not specified it in any way) is determined by our first ending arc -(R * 2), given the radius that the arc itself has.
M (CX + R), CY
a R,R 0 1,0 -(R * 2),0
a R,R 0 1,0 (R * 2),0
Same applies as the previous path.
M CX CY
m -R, 0
a R,R 0 1,0 (R * 2),0
a R,R 0 1,0 -(R * 2),0
Since we are ending at (R * 2 ) in our first arc, we will obviously be starting at the opposite position. In other words, this one starts where our previous two paths ended.
M (CX - R), CY
a R,R 0 1,1 (R * 2),0
a R,R 0 1,1 -(R * 2),0
This starts in the same position as the last one due to (R * 2), but it is running clockwise because we have set the sweep-flag property (marked in yellow) to 1.

We‘ve seen how to wrap text on a single path in a circle. Let’s now take a look at how we can break up that path into two paths and the benefits you can get from that.

Breaking Our Paths Into Parts

There are a lot of things you can do with the text in your path, i.e. achieving stylistic effects with tspan elements, setting the offset of the text, or animating the text. Basically, whatever you do will be constrained by the path itself. But by breaking up our multiarc paths into single arc paths, we can play around with the direction of our text, the z-indexing of different parts of our text, and achieving more complex animations.

First, we are going to want to use another SVG image to show some of the effects. I will be using the diamond from the article on pointer events which I mentioned earlier. First, let’s show what it will look like with a single path circular text laid on top of it.

Let’s assume that our circle is CX 295, CY 200, R 175. Now, following the Circular path method, we now see the following:

M (CX - R), CY
a R,R 0 1,1 (R * 2),0
a R,R 0 1,1 -(R * 2),0

See the Pen SVG Amethyst by Bryan Rasmussen.

I’m not going to talk about the path or the text size, fill or stroke color. We should all understand that by now, and be able to make it be whatever we want it to be. But by looking at the text, we can see some downsides or limitations right away:

  • The text all runs in one direction;
  • It might be nice to have some of the text go behind the amethyst, especially where it says MAGAZINE. In order to make the ‘M’ and ‘E’ line up on the circle, the ‘A’ has to be on the side lower point of the amethyst, which feels sort of unbalanced in another way. (I feel like the ‘A’ should be precisely positioned and pointing down at that point.)

If we want to fix these issues, we need to split our single path into two. In the following pen, I have separated the path into two paths, (and placed them into the defs area of the SVG for our textPaths to reference):

See the Pen SVG Amethyst two paths by Bryan Rasmussen.

Again, assuming our CX is 295, CY 200, R 175, then the two paths are in the format of the following (for the top semicircular path):

M (CX - R), CY
a R,R 0 1,1 (R * 2),0

And the following for the bottom:

M (CX + R), CY
a R,R 0 1,1 -(R * 2),0

However, we still have circular text that moves all in the same direction. To fix that for everything but Edge, all you have to do is to add the side="right" attribute to the text element that holds the ‘MAGAZINE’ textPath.

Making The Text Go Another Direction

If we want to support as many browsers as we can, we have to alter the path and not rely on the side attribute which is not fully supported. What we can do is to copy our top semicircle path, but change the sweep from 1 to 0:

Before:

M 120, 200
a 175,175 0 1,1 350,0

After:

M 120, 200
a 175,175 0 1,0 350,0

But our text is now drawn on the inner circle defined by the sweep and it won’t look so nice in different browsers. This means that we’re going to have to move the position of our path to align with the ‘S’ of ‘Smashing’, make the ending X of the path greater, and set some offset to the text. As you can see, there is also a little text difference between Firefox and the others which we can improve by increasing the textLength attribute on the text element, as well as removing whitespace from the textPath (since Firefox evidently thinks whitespace is meaningful).

The solution:

See the Pen SVG Amethyst two paths fixed by Bryan Rasmussen.

Change The Z-Index Of Part Of Our Circular Text

Finally, we want to make our text goes both in front and behind the amethyst. Well, that’s easy. Remember that SVG’s z-indexing of element is based by where they are in the markup? So if we have two elements, element 1 will be drawn behind element 2. Next, all we have to do is to move a text element up in our SVG markup so it is drawn before the amethyst.

You can see the result below in which parts of the word ‘MAGAZINE’ are hidden by the lower point of the amethyst.

See the Pen SVG Amethyst two paths z-index by Bryan Rasmussen.

If you take a look at the markup, you can see that the lower semicircle of text has been moved to be before the path that draws the amethyst.

Animating The Parts Of Our Circle

So now we have the ability to make circular text by completely controlling the directionality of the parts of our text by putting the text into two semicircles. This can, of course, also be exploited to make animations of the text. Making cross-browser SVG animations is really the subject of another article (or a lot more articles). These examples will only work in Chrome and Firefox because of using the SMIL-animations syntax instead of CSS keyframes or tools like Greensock. But it gives a good indicator of the effects you can achieve by animating the decomposed circle.

Take the following pen:

See the Pen SVG Amethyst two paths animated by Bryan Rasmussen.

Please press the ‘Rerun’ button on the codepen to see the animation in action. The two parts of our circular text begin animating at the same time, but have a different duration so they end at different times. Because we are animating the textLength attribute, we have put two animate directives under each text — one for the text element (so Firefox will work) and one for the textpath element (so Chrome will work).

Conclusion

In this article, we’ve seen how to turn a circle into a path and back again, in order to better understand when we need to optimize a path and when not. We’ve seen how turning the circle into a path frees us up to placing the text on the circular path, but also how to further split the circular path into semicircles and gain fuller control over directionality and animation of the component parts of our circular text.

Further Reading on SmashingMag:

Smashing Editorial(dm, ra, yk, il)
Categories: Others Tags:

8 Dead Web Trends We’Re Better Off Without

March 22nd, 2019 No comments

A lot of very smart people have said that those who do not learn from history are doomed to repeat it. Other people say that nostalgia bait…ahem…“retrospectives” get a lot of clicks. They’re both right, and I thought it might be both fun and educational to take a look at some of the dead UI conventions of yesteryear.

It’s good to remember why they ever lived, and how and why they died. It gives us insight into how best practices were born, and why they are the “best” practices we have. It gives us context for building the future of the web. And besides, the industry gets newbies every year, and they should know some of this stuff, too.

I’m glad they don’t have to suffer what we suffered, but they should know what we suffered. Friends, countrymen, Romans: we come here not to mourn these UI conventions and browser features, but to bury them.

Shhh… shhhh… it’s okay. It’s okay. It’s dead, and it can’t hurt you anymore.

For everyone who didn’t have a visceral reaction just now, the tag made things do just that: blink. Off and on, there and not there. It’s almost like it was designed to hurt your eyes. Invented during the height of the early Browser Wars, the tag was an early example of browser-specific tags, and was meant to give Netscape Navigator an edge over the then-nascent Internet Explorer.

I’m not saying that this decision killed Netscape Navigator, or that NN deserved to die because of it, but that’s exactly what I’m saying.

(Screenshot not provided for obvious reasons.)

Flash Menus

I was guilty as hell of this one. That’s right, in the beginning, I couldn’t get the hand of animated navigation menus (read: menus having a hover state) built with JavaScript. Or DHTML, as it was called in Dreamweaver. So I used Macromedia Flash to create super fancy menus with animated buttons, and embedded them into every website header I made.

I was hardly the only one. For a while, Flash-based menu templates were a cottage industry on their own. Hot tip: Never, EVER make a website menu that you can’t change by just editing a text file. The maintenance cost alone was a massive headache, and search engines never did get the hang of crawling through Flash files.

Thank God for :hover.

Frames, the Original AJAX

That’s right, kids. Before we used JavaScript to load all of our data in progressive web apps, the browser did all the work, and presumably still can. You know iframes? They used to have a big brother that was just called “Frames”, and that’s what we used for basic layouts before Tables came along.

The problem was that even as they allowed the browser to update just part of a page instead of loading a whole new one (sort of), they also broke several fundamental browser features, including:

  • The Back and Forward buttons;
  • Browser history in general;
  • It was harder to copy-paste links to specific pages in a site;
  • Reloading a website certainly got a bit random, and would usually just take you back to the “home page”, as it were.

Eventually the whole frameset feature was deprecated, and people mostly use iframes to load Embed content like YouTube videos.

Image Buttons

Not long ago, flat design took the world by storm, prompting people to say things like, “Wait… is that a button? Can I click it? WHY would you even use a custom cursor, there?” Before that, everyone was all about those 3D-looking buttons. Because they were fancy. Look, it started in the early ‘90s, and we just don’t question anything that happened between 1980 and 2005. They were just different times.

Of course, CSS3 has all but killed the image-based button. When the text was baked into the image, the buttons were impossible to manage, and when it wasn’t, we had to use a thousand tricks to make them even semi-responsive. Anyone remember making a button that was one-part loooooong PNG, and one part the corners on the right? Or having a separate PNG for each corner of a button?

God, we have it good, nowadays.

Marquee

Once upon a time, before everyone started saying that image sliders were bad, HTML actually had a built-in element for making things slide around the page. It was called the Marquee tag, and people hated it long before it actually died. It was implemented in Internet Explorer in response to the tag, and was only marginally less bad for your eyes.

It was abandoned and deprecated for being too distracting to users.

Demos available here: https://www.quackit.com/html/codes/html_marquee_code.cfm

Fancy Separators/Page Dividers

Back before we ever started doing proper layout, people needed a way to break up long pages of text to make them less awkward to read. The


element felt a little too plain to some, and so the designers of the day resorted—as they so often did—to .GIFs. They were just horizontal bars at heart, but as long as they were images, the could be as fancy as you liked.

This was another trend that fell to CSS/CSS3. Besides, when the divider is so much more visually exciting than the text, that can prove to be a bit of a conflict for the user.

The Site Map

The Site Map used to be the be-all and end-all of navigation. Before websites had their own search functions, you used the site map to find what you needed on any given site. It was simple: all of the content was right there, and you just needed to scroll.

Nowadays, a lot of sites are just too big, or too small, to ever need a site map as a UI element. They are typically still auto-generated by many CMS, but they’re used to help search engines crawl the site faster. For medium-sized websites, I think this is a feature that could come back, and it wouldn’t hurt anybody.

Table Layout

Ah, table layout. It’s mocked in jokes by some, referenced only in hushed whispers by others. It was the trend that pushed web design forward, and the one that couldn’t die fast enough. It combined inline styles, a fair amount of confusion, and 1-pixel .GIF files that somehow held the layouts together.

I never really got that part.

It died because CSS had floats. And though that was a dirty hack and a half, it was actually better. We only used tables for so long because browsers (cough IE cough) were slow to catch up with CSS support, but they got there eventually.

The weird thing is that CSS Grid is like having tables back again, but so much better.

Yeah, I had to use the Wayback Machine. Googling for table layouts gets you a whole bunch of articles about why you shouldn’t use them.

Add Realistic Chalk and Sketch Lettering Effects with Sketch’it – only $5!

Source

Categories: Designing, Others Tags:

Using for Menus and Dialogs is an Interesting Idea

March 21st, 2019 No comments

One of the most empowering things you can learn as a new front-end developer who is starting to learn JavaScript is to change classes. If you can change classes, you can use your CSS skills to control a lot on a page. Toggle a class to one thing, style it this way, toggle to another class (or remove it) and style it another way.

But there is an HTML element that also does toggles!

! For example, it’s definitely the quickest way to build an accordion UI.

Extending that toggle-based thinking, what is a user menu if not for a single accordion? Same with modals. If we went that route, we could make JavaScript optional on those dynamic things. That’s exactly what GitHub did with their menu.

Inside the

element, GitHub uses some Web Components (that do require JavaScript) to do some bonus stuff, but they aren’t required for basic menu functionality. That means the menu is resilient and instantly interactive when the page is rendered.

Mu-An Chiou, a web systems engineer at GitHub who spearheaded this, has a presentation all about this!

We went all in on details to turn a lot of things interactive without JS. There is also https://t.co/SFXtkNzIbZ, and here is a talk I gave on leveraging the power of details, which mentions the CSS trick 🙂 https://t.co/DmX8opvi4z

Happy to talk/share about about any of these!

— Mu-An Chiou (@muanchiou) February 1, 2019

The worst strike on

is its browser support in Edge, but I guess we won’t have to worry about that soon, as Edge will be using Chromium… soon? Does anyone know?

The post Using

for Menus and Dialogs is an Interesting Idea appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

Technical Debt is Like Tetris

March 21st, 2019 No comments

Here’s a wonderful post by Eric Higgins all about refactoring and technical debt. He compares giant refactoring projects to being similar to Tetris:

Similar to running a business, Tetris gets harder the longer you play. Pieces move faster and it becomes harder to keep up.

Similar to running a business, you can never win Tetris. There is no true finish line. You only control how quickly you lose.

Similar to running a business, allowing too many gaps to build up in Tetris will cause you to lose.

I love this comparison, despite my mediocre Tetris skills. It does feel like even “easy” development becomes harder as technical debt grows on a project, much the same way Tetris pieces gain speed and provide little time to react as the stack grows. However, I do think perhaps I have a more optimistic view of technical debt overall. If you work slowly and carefully then you can build up a culture of refactoring and gather momentum over time.

Direct Link to ArticlePermalink

The post Technical Debt is Like Tetris appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

It’s pretty cool how Netlify CMS works with any flat file site generator

March 21st, 2019 No comments

Little confession here: when I first saw Netlify CMS at a glance, I thought: cool, maybe I’ll try that someday when I’m exploring CMSs for a new project. Then as I looked at it with fresh eyes: I can already use this! It’s a true CMS in that it adds a content management UI on top of any static site generator that works from flat files! Think of how you might build a site from markdown files with Gatsby, Jekyll, Hugo, Middleman, etc. You can create and edit Markdown files and the site’s build process runs and the site is created.

Netlify CMS gives you (or anyone you set it up for) a way to create/edit those Markdown files without having to use a code editor or know about Pull Requests on GitHub or anything. It’s a little in-browser app that gives you a UI and does the file manipulation and Git stuff behind the scenes.

Here’s an example.

Our conferences website is a perfect site to build with a static site generator.

It’s on GitHub, so it’s open to Pull Requests, and each conference is a Markdown file.

That’s pretty cool already. The community has contributed 77 Pull Requests already really fleshing out the content of the site, and the design, accessibility, and features as well!

I used 11ty to build the site, which works great with building out those Markdown files into a site, using Nunjucks templates. Very satisfying combo, I found, after a slight mostly configuration-related learning curve.

Enter Netlify CMS.

But as comfortable as you or I might be with a quick code edit and Pull Request, not everybody is. And even I have to agree that going to a URL quick, editing some copy in input fields, and clicking a save button is the easiest possible way to manage content.

That CMS UI is exactly what Netlify CMS gives you. Wanna see the entire commit for adding Netlify CMS?

It’s two files! That still kinda blows my mind. It’s a little SPA React app that’s entirely configurable with one file.

Cutting to the chase here, once it is installed, I now have a totally customized UI for editing the conferences on the site available right on the production site.

Netlify CMS doesn’t do anything forceful or weird, like attempt to edit the HTML on the production site directly. It works right into the workflow in the same exact way that you would if you were editing files in a code editor and committing in Git.

Auth & Git

You use Netlify CMS on your production site, which means you need authentication so that just you (and the people you want) have access to it. Netlify Identity makes that a snap. You just flip it on from your Netlify settings and it works.

I activated GitHub Auth so I could make logging in one-click for me.

The Git magic happens through a technology called Git Gateway. You don’t have to understand it (I don’t really), you just enable it in Netlify as part of Netlify Identity, and it forms the connection between your site and the Git repository.

Now when you create/edit content, actual Markdown files are created and edited (and whatever else is involved, like images!) and the change happens right in the Git repository.


I made this the footer of the site cause heck yeah.

The post It’s pretty cool how Netlify CMS works with any flat file site generator appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

The hottest UX and UI tools you’ll want to check out today

March 21st, 2019 No comments

Companies
used to place the highest priority on delivering bug-free software. There’s
nothing wrong with that of course. Bug-free software is a worthy goal. The
problem is, these same companies neglected to address usability issues. They
did that until their software solutions had been placed into service. At this
time, resolving usability issues often involved significant expense.

Usability issues tend to be more common than
many people realize. The good news is, they can be addressed during the design
phase. Then, they can usually be dealt with at minimal time and expense.

The tools and resources presented here are
precisely what you want to have access to. This is if your intent is to address
usability issues or concerns during the design phase.

Let’s start with:

1. Mason

Mason provides teams with a visual front-end
feature-building environment in which they can collaborate to create app and
other digital product features and seamlessly integrate those features into the
codebases of existing products.

Normally, it can be fairly expensive to make a
change or add a feature to an existing product, even if either is relatively
minor. With Mason, neither software product teams or clients are forced to wait
for the next deployment cycle to make a change or add a feature. Mason makes
software maintenance far easier, faster, and less expensive than what people
have become accustomed to.

Since Mason takes care of the coding,
developers can focus on larger and more complex tasks and leave it up to the
designers to create and deploy robust and secure software features and fixes.

Mason is easy to work with, transparent, and
completely non-intrusive, it’s there only when you trigger it, so it won’t wear
your websites or apps down.

2. UXPin

UXPin
is a powerful cloud-based prototyping tool you can use from the very beginning
of the design phase until it’s time to hand the design over to the developers.
Design, prototype, collaborate all in one place. It’s one of the few design
tools that allows you to design from coded components. Its use is based on the principle that one of
the best ways to create a top-tier UX is by using an iterative wireframing and
prototyping approach.

UXPin enables teams to share design information
as needed, including with their clients and other project stakeholders.

This design information can take the form of
concept-proving wireframes, low-fidelity prototypes to share with others for
feedback, and interactive high-fidelity prototypes that have the look and feel
of the real thing for user testing and design approval and buy off.

UXPin is designed to help you significantly
reduce design, development, and product time, while ensuring consistency of
design from one software solution to the next.

3. Interfacer

Interfacer
not only offers a practical solution to your need for a comprehensive library
of design aids, but it’s a fun resource to have at your fingertips as well.
This resource “library” is actually a collection of mini-libraries that feature
hundreds of Google fonts, icons, UI Design kits, images, and templates for
websites, landing pages, special pages and more.

Best of all, the quality throughout is
excellent and everything is free for commercial use.

4. Webflow

There’s no need to invest in separate tools or platforms to manage your website design, development, and hosting needs. Webflow gives you a code-free, all-in-one solution that includes building a client-friendly CMS for each website or app you create — not to mention completely custom layouts and interactions. Designed for creative web designers, Webflow is easy to work with whether you’re a long-time designer or a beginner.

Why should you concern yourself over UX design and these tools?

A
friendly UX encourages visitors to see their journey to its end

We’ve all been there. Looking for a particular
product or service on the web and finding several good websites.

They promote the same products or services, but
one stands out from the others. It was quick and easy to find what we were
looking for. The experience was in fact so enjoyable that we decided to
bookmark the website.

Wouldn’t it be wonderful if the website or app
in question belonged to you?

A
compelling UX design can increase loyalty to your brand

The ability to provide a good user experience
is key to the success of your business. It helps to build trust in your brand,
your product line, or your service. The UX you design can also help to
establish longstanding customer relationships. Seamless and enjoyable
interactions that encourage customer participation should be your goal.

Great
UX designs can encourage word of mouth referrals to new customers

Word of mouth has always been an effective way
to generate new business, and it’s no different in the digital world. People
like to share pleasant experiences. Provide a good online experience to your
users and they’ll want to tell the world about it.

Make the buying process a smooth and pleasant
one for your users. An opportunity will arise to recommend someone to friends
over social media. Whose business will that be? Yours?

Conclusion

The four UX and UI tools and resources
described above have several things in common. If you make a choice, it most
likely be one that best addresses your particular needs.

These UX/UI tools are fast, efficient, and
effective. The collection of design aid mini-libraries should help you. You can
steer clear having to reinvent the wheel.
You can also avoid extensive searches for special fonts, icons, or UI
kits.

Read More at The hottest UX and UI tools you’ll want to check out today

Categories: Designing, Others Tags:

Encapsulating Style and Structure with Shadow DOM

March 21st, 2019 No comments

This is part four of a five-part series discussing the Web Components specifications. In part one, we took a 10,000-foot view of the specifications and what they do. In part two, we set out to build a custom modal dialog and created the HTML template for what would evolve into our very own custom HTML element in part three.

Article Series:

  1. An Introduction to Web Components
  2. Crafting Reusable HTML Templates
  3. Creating a Custom Element from Scratch
  4. Encapsulating Style and Structure with Shadow DOM (This post)
  5. Advanced Tooling for Web Components (Coming soon!)

If you haven’t read those articles, you would be advised to do so now before proceeding in this article as this will continue to build upon the work we’ve done there.

When we last looked at our dialog component, it had a specific shape, structure and behaviors, however it relied heavily on the outside DOM and required that the consumers of our element would need to understand it’s general shape and structure, not to mention authoring all of their own styles (which would eventually modify the document’s global styles). And because our dialog relied on the contents of a template element with an id of “one-dialog”, each document could only have one instance of our modal.

The current limitations of our dialog component aren’t necessarily bad. Consumers who have an intimate knowledge of the dialog’s inner workings can easily consume and use the dialog by creating their own element and defining the content and styles they wish to use (even relying on global styles defined elsewhere). However, we might want to provide more specific design and structural constraints on our element to accommodate best practices, so in this article, we will be incorporating the shadow DOM to our element.

What is the shadow DOM?

In our introduction article, we said that the shadow DOM was “capable of isolating CSS and JavaScript, almost like an .” Like an , selectors and styles inside of a shadow DOM node don’t leak outside of the shadow root and styles from outside the shadow root don’t leak in. There are a few exceptions that inherit from the parent document, like font family and document font sizes (e.g. rem) that can be overridden internally.

Unlike an , however, all shadow roots still exist in the same document so that all code can be written inside a given context but not worry about conflicts with other styles or selectors.

Adding the shadow DOM to our dialog

To add a shadow root (the base node/document fragment of the shadow tree), we need to call our element’s attachShadow method:

class OneDialog extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.close = this.close.bind(this);
  }
}

By calling attachShadow with mode: 'open', we are telling our element to save a reference to the shadow root on the element.shadowRoot property. attachShadow always returns a reference to the shadow root, but here we don’t need to do anything with that.

If we had called the method with mode: 'closed', no reference would have been stored on the element and we would have to create our own means of storage and retrieval using a WeakMap or Object, setting the node itself as the key and the shadow root as the value.

const shadowRoots = new WeakMap();

class ClosedRoot extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'closed' });
    shadowRoots.set(this, shadowRoot);
  }

  connectedCallback() {
    const shadowRoot = shadowRoots.get(this);
    shadowRoot.innerHTML = `<h1>Hello from a closed shadow root!</h1>`;
  }
}

We could also save a reference to the shadow root on our element itself, using a Symbol or other key to try to make the shadow root private.

In general, the closed mode for shadow roots exists for native elements that use the shadow DOM in their implementation (like or ). Further, for unit testing our elements, we might not have access to the shadowRoots object, making it unable for us to target changes inside our element depending on how our library is architected.

There might be some legitimate use cases for user-land closed shadow roots, but they are few and far between, so we’ll stick with the open shadow root for our dialog.

After implementing the new open shadow root, you might notice now that our element is completely broken when we try to run it:

See the Pen
Dialog example using template with shadow root
by Caleb Williams (@calebdwilliams)
on CodePen.

This is because all of the content we had before was added to and manipulated in the traditional DOM (what we’ll call the light DOM). Now that our element has a shadow DOM attached, there is no outlet for the light DOM to render. Let’s start fixing these issues by moving our content to the shadow DOM:

class OneDialog extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.close = this.close.bind(this);
  }
  
  connectedCallback() {
    const { shadowRoot } = this;
    const template = document.getElementById('one-dialog');
    const node = document.importNode(template.content, true);
    shadowRoot.appendChild(node);
    
    shadowRoot.querySelector('button').addEventListener('click', this.close);
    shadowRoot.querySelector('.overlay').addEventListener('click', this.close);
    this.open = this.open;
  }

  disconnectedCallback() {
    this.shadowRoot.querySelector('button').removeEventListener('click', this.close);
    this.shadowRoot.querySelector('.overlay').removeEventListener('click', this.close);
  }
  
  set open(isOpen) {
    const { shadowRoot } = this;
    shadowRoot.querySelector('.wrapper').classList.toggle('open', isOpen);
    shadowRoot.querySelector('.wrapper').setAttribute('aria-hidden', !isOpen);
    if (isOpen) {
      this._wasFocused = document.activeElement;
      this.setAttribute('open', '');
      document.addEventListener('keydown', this._watchEscape);
      this.focus();
      shadowRoot.querySelector('button').focus();
    } else {
      this._wasFocused && this._wasFocused.focus && this._wasFocused.focus();
      this.removeAttribute('open');
      document.removeEventListener('keydown', this._watchEscape);
    }
  }
  
  close() {
    this.open = false;
  }
  
  _watchEscape(event) {
    if (event.key === 'Escape') {
        this.close();   
    }
  }
}

customElements.define('one-dialog', OneDialog);

The major changes to our dialog so far are actually relatively minimal, but they carry a lot of impact. For starters, all our our selectors (including our style definitions) are internally scoped. For example, our dialog template only has one button internally, so our CSS only targets button { ... }, and those styles don’t bleed out to the light DOM.

We are, however, still reliant on the template that is external to our element. Let’s change that by removing the markup from our template and dropping it into our shadow root’s innerHTML.

See the Pen
Dialog example using only shadow root
by Caleb Williams (@calebdwilliams)
on CodePen.

Including content from the light DOM

The shadow DOM specification includes a means for allowing content from outside the shadow root to be rendered inside of our custom element. For those of you who remember AngularJS, this is a similar concept to ng-transclude or using props.children in React. With Web Components, this is done using the element.

A simple example would look like this:

<div>
  <span>world <!-- this would be inserted into the slot element below --></span>
  <#shadow-root><!-- pseudo code -->
    <p>Hello <slot></slot></p>
  </#shadow-root>
</div>

A given shadow root can have any number of slot elements, which can be distinguished with a name attribute. The first slot inside of the shadow root without a name, will be the default slot and all content not otherwise assigned will flow inside that node. Our dialog really needs two slots: a heading and some content (which we’ll make default).

See the Pen
Dialog example using shadow root and slots
by Caleb Williams (@calebdwilliams)
on CodePen.

Go ahead and change the HTML portion of our dialog and see the result. Any content inside of the light DOM is inserted into the slot to which it is assigned. Slotted content remains inside the light DOM although it is rendered as if it were inside the shadow DOM. This means that these elements are still fully style-able by a consumer who might want to control the look and feel of their content.

A shadow root’s author can style content inside the light DOM to a limited extent using the CSS ::slotted() pseudo-selector; however, the DOM tree inside slotted is collapsed, so only simple selectors will work. In other words, we wouldn’t be able to style a element inside a

element within the flattened DOM tree in our previous example.

The best of both worlds

Our dialog is in a good state now: it has encapsulated, semantic markup, styles and behavior; however, some consumers of our dialog might still want to define their own template. Fortunately, by combining two techniques we’ve already learned, we can allow authors to optionally define an external template.

To do this, we will allow each instance of our component to reference an optional template ID. To start, we need to define a getter and setter for our component’s template.

get template() {
  return this.getAttribute('template');
}

set template(template) {
  if (template) {
    this.setAttribute('template', template);
  } else {
    this.removeAttribute('template');
  }
  this.render();
}

Here we’re doing much the same thing that we did with our open property by tying it directly to its corresponding attribute. But at the bottom, we’re introducing a new method to our component: render. We are going to use our render method to insert our shadow DOM’s content and remove that behavior from the connectedCallback; instead, we will call render when our element is connected:

connectedCallback() {
  this.render();
}

render() {
  const { shadowRoot, template } = this;
  const templateNode = document.getElementById(template);
  shadowRoot.innerHTML = '';
  if (templateNode) {
    const content = document.importNode(templateNode.content, true);
    shadowRoot.appendChild(content);
  } else {
    shadowRoot.innerHTML = `<!-- template text -->`;
  }
  shadowRoot.querySelector('button').addEventListener('click', this.close);
  shadowRoot.querySelector('.overlay').addEventListener('click', this.close);
  this.open = this.open;
}

Our dialog now has some really basic default stylings, but also gives consumers the ability to define a new template for each instance. If we wanted, we could even use attributeChangedCallback to make this component update based on the template it’s currently pointing to:

static get observedAttributes() { return 'open', 'template']; }

attributeChangedCallback(attrName, oldValue, newValue) {
  if (newValue !== oldValue) {
    switch (attrName) {
      /** Boolean attributes */
      case 'open':
        this[attrName] = this.hasAttribute(attrName);
        break;
      /** Value attributes */
      case 'template':
        this[attrName] = newValue;
        break;
    }
  }
}

See the Pen
Dialog example using shadow root, slots and template
by Caleb Williams (@calebdwilliams)
on CodePen.

In the demo above, changing the template attribute on our element will alter which design is being used when the element is rendered.

Strategies for styling the shadow DOM

Currently, the only reliable way to style a shadow DOM node is by adding a element to the shadow root’s inner HTML. This works fine in almost every case as browsers will de-duplicate stylesheets across these components, where possible. This does tend to add a bit of memory overhead, but generally not enough to notice.

Inside of these style tags, we can use CSS custom properties to provide an API for styling our components. Custom properties can pierce the shadow boundary and effect content inside a shadow node.

“Can we use a element inside of a shadow root?” you might ask. And, in fact, we can. The trouble comes when trying to reuse this component across multiple applications as the CSS file might not be saved in a consistent location throughout all apps. However, if we are certain as to the element’s stylesheet location, then using is an option. The same holds true for including an @import rule in a style tag.

CSS custom properties

One of the benefits of using CSS custom properties — also called CSS variables — is that they bleed through the shadow DOM. This is by design, giving component authors a surface for allowing theming and styling of their components from the outside. It is important to note, however, that since CSS cascades, changes to custom properties made inside a shadow root do not bleed back up.

See the Pen
CSS custom properties and shadow DOM
by Caleb Williams (@calebdwilliams)
on CodePen.

Go ahead and comment out or remove the variables set in the CSS panel of the demo above and see how this impacts the rendered content. Afterward, you can take a look at the styles in the shadow DOM’s innerHTML, you’ll see how the shadow DOM can define its own property that won’t affect the light DOM.

Constructible stylesheets

At the time of this writing, there is a proposed web feature that will allow for more modular styling of shadow DOM and light DOM elements using constructible stylesheets that has already landed in Chrome 73 and received positive signaling from Mozilla.

This feature would allow authors to define stylesheets in their JavaScript files similar to how they would write normal CSS and share those styles across multiple nodes. So, a single stylesheet could be appended to multiple shadow roots and potentially the document as well.

const everythingTomato = new CSSStyleSheet();
everythingTomato.replace('* { color: tomato; }');

document.adoptedStyleSheets = [everythingTomato];

class SomeCompoent extends HTMLElement {
  constructor() {
    super();
    this.adoptedStyleSheets = [everythingTomato];
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `<h1>CSS colors are fun</h1>`;
  }
}

In the above example, the everythingTomato stylesheet would be simultaneously applied to the shadow root and to the document’s body. This feature would be very useful for teams creating design systems and components that are intended to be shared across multiple applications and frameworks.

In the next demo, we can see a really basic example of how this can be utilized and the power that constructble stylesheets offer.

See the Pen
Construct style sheets demo
by Caleb Williams (@calebdwilliams)
on CodePen.

In this demo, we construct two stylesheets and append them to the document and to the custom element. After three seconds, we remove one stylesheet from our shadow root. For those three seconds, however, the document and the shadow DOM share the same stylesheet. Using the polyfill included in that demo, there are actually two style elements present, but Chrome Canary runs this natively.

That demo also includes a form for showing how a sheet’s rules can easily and effectively changed asynchronously as needed. This addition to the web platform can be a powerful ally for those creating design systems that span multiple frameworks or site authors who want to provide themes for their websites.

There is also a proposal for CSS Modules that could eventually be used with the adoptedStyleSheets feature. If implemented in its current form, this proposal would allow importing CSS as a module much like ECMAScript modules:

import styles './styles.css';

class SomeCompoent extends HTMLElement {
  constructor() {
    super();
    this.adoptedStyleSheets = [styles];
  }
}

Part and theme

Another feature that is in the works for styling Web Components are the ::part() and ::theme() pseudo-selectors. The ::part() specification will allow authors to define parts of their custom elements that have a surface for styling:

class SomeOtherComponent extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>h1 { color: rebeccapurple; }</style>
      <h1>Web components are <span part="description">AWESOME</span></h1>
    `;
  }
}
    
customElements.define('other-component', SomeOtherComponent);

In our global CSS, we could target any element that has a part called description by invoking the CSS ::part() selector.

other-component::part(description) {
  color: tomato;
}

In the above example, the primary message of the

tag would be in a different color than the description part, giving custom element authors the ability to expose styling APIs for their components and maintain control over the pieces they want to maintain control over.

The difference between ::part() and ::theme() is that ::part() must be specifically selected whereas ::theme() can be nested at any level. The following would have the same effect as the above CSS, but would also work for any other element that included a part="description" in the entire document tree.

:root::theme(description) {
  color: tomato;
}

Like constructible stylesheets, ::part() has landed in Chrome 73.

Wrapping up

Our dialog component is now complete, more-or-less. It includes its own markup, styles (without any outside dependencies) and behaviors. This component can now be included in projects that use any current or future frameworks because they are built against the browser specifications instead of third-party APIs.

Some of the core controls are a little verbose and do rely on at least a moderate knowledge of how the DOM works. In our final article, we will discuss higher-level tooling and how to incorporate with popular frameworks.

Article Series:

  1. An Introduction to Web Components
  2. Crafting Reusable HTML Templates
  3. Creating a Custom Element from Scratch
  4. Encapsulating Style and Structure with Shadow DOM (This post)
  5. Advanced Tooling for Web Components (Coming soon!)

The post Encapsulating Style and Structure with Shadow DOM appeared first on CSS-Tricks.

Categories: Designing, Others Tags:

How To Make A Speech Synthesis Editor

March 21st, 2019 No comments
Sanity.io's default editor for Portable Text

How To Make A Speech Synthesis Editor

How To Make A Speech Synthesis Editor

Knut Melvær

2019-03-21T13:00:16+01:002019-03-22T07:05:47+00:00

When Steve Jobs unveiled the Macintosh in 1984, it said “Hello” to us from the stage. Even at that point, speech synthesis wasn’t really a new technology: Bell Labs developed the vocoder as early as in the late 30s, and the concept of a voice assistant computer made it into people’s awareness when Stanley Kubrick made the vocoder the voice of HAL9000 in 2001: A Space Odyssey (1968).

It wasn’t before the introduction of Apple’s Siri, Amazon Echo, and Google Assistant in the mid 2015s that voice interfaces actually found their way into a broader public’s homes, wrists, and pockets. We’re still in an adoption phase, but it seems that these voice assistants are here to stay.

In other words, the web isn’t just passive text on a screen anymore. Web editors and UX designers have to get accustomed to making content and services that should be spoken out loud.

We’re already moving fast towards using content management systems that let us work with our content headlessly and through APIs. The final piece is to make editorial interfaces that make it easier to tailor content for voice. So let’s do just that!

What Is SSML

While web browsers use W3C’s specification for HyperText Markup Language (HTML) to visually render documents, most voice assistants use Speech Synthesis Markup Language (SSML) when generating speech.

A minimal example using the root element , and the paragraph (

) and sentence () tags:

<speak>
  <p>
    <s>This is the first sentence of the paragraph.</s>
    <s>Here's another sentence.</s>
  </p>
</speak>
Press play to listen to the snippet:

Where SSML gets existing is when we introduce tags for and (pitch):

<speak>
  <p>
    <s>Put some <emphasis strength="strong">extra weight on these words</emphasis></s>
    <s>And say <prosody pitch="high" rate="fast">this a bit higher and faster</prosody>!</s>
  </p>
</speak>
Press play to listen to the snippet:

SSML has more features, but this is enough to get a feel for the basics. Now, let’s take a closer look at the editor that we will use to make the speech synthesis editing interface.

The Editor For Portable Text

To make this editor, we’ll use the editor for Portable Text that features in Sanity.io. Portable Text is a JSON specification for rich text editing, that can be serialized into any markup language, such as SSML. This means you can easily use the same text snippet in multiple places using different markup languages.


Sanity.io's default editor for Portable Text
Sanity.io’s default editor for Portable Text (Large preview)

Installing Sanity

Sanity.io is a platform for structured content that comes with an open-source editing environment built with React.js. It takes two minutes to get it all up and running.

Type npm i -g @sanity/cli && sanity init into your terminal, and follow the instructions. Choose “empty”, when you’re prompted for a project template.

If you don’t want to follow this tutorial and make this editor from scratch, you can also clone this tutorial’s code and follow the instructions in README.md.

When the editor is downloaded, you run sanity start in the project folder to start it up. It will start a development server that use Hot Module Reloading to update changes as you edit its files.

How To Configure Schemas In Sanity Studio

Creating The Editor Files

We’ll start by making a folder called ssml-editor in the /schemas folder. In that folder, we’ll put some empty files:

/ssml-tutorial/schemas/ssml-editor
                        ├── alias.js
                        ├── emphasis.js
                        ├── annotations.js
                        ├── preview.js
                        ├── prosody.js
                        ├── sayAs.js
                        ├── blocksToSSML.js
                        ├── speech.js
                        ├── SSMLeditor.css
                        └── SSMLeditor.js

Now we can add content schemas in these files. Content schemas are what defines the data structure for the rich text, and what Sanity Studio uses to generate the editorial interface. They are simple JavaScript objects that mostly require just a name and a type.

We can also add a title and a description to make a bit nicer for editors. For example, this is a schema for a simple text field for a title:

export default {
  name: 'title',
  type: 'string',
  title: 'Title',
  description: 'Titles should be short and descriptive'
}

Sanity Studio with a title field and an editor for Portable Text
The studio with our title field and the default editor (Large preview)

Portable Text is built on the idea of rich text as data. This is powerful because it lets you query your rich text, and convert it into pretty much any markup you want.

It is an array of objects called “blocks” which you can think of as the “paragraphs”. In a block, there is an array of children spans. Each block can have a style and a set of mark definitions, which describe data structures distributed on the children spans.

Sanity.io comes with an editor that can read and write to Portable Text, and is activated by placing the block type inside an array field, like this:

// speech.js
export default {
  name: 'speech',
  type: 'array',
  title: 'SSML Editor',
  of: [
    { type: 'block' }
  ]
}

An array can be of multiple types. For an SSML-editor, those could be blocks for audio files, but that falls outside of the scope of this tutorial.

The last thing we want to do is to add a content type where this editor can be used. Most assistants use a simple content model of “intents” and “fulfillments”:

  • Intents
    Usually a list of strings used by the AI model to delineate what the user wants to get done.
  • Fulfillments
    This happens when an “intent” is identified. A fulfillment often is — or at least — comes with some sort of response.

So let’s make a simple content type called fulfillment that use the speech synthesis editor. Make a new file called fulfillment.js and save it in the /schema folder:

// fulfillment.js
export default {
  name: 'fulfillment',
  type: 'document',
  title: 'Fulfillment',
  of: [
    {
      name: 'title',
      type: 'string',
      title: 'Title',
      description: 'Titles should be short and descriptive'
    },
    {
      name: 'response',
      type: 'speech'
    }
  ]
}

Save the file, and open schema.js. Add it to your studio like this:

// schema.js
import createSchema from 'part:@sanity/base/schema-creator'
import schemaTypes from 'all:part:@sanity/base/schema-type'
import fullfillment from './fullfillment'
import speech from './speech'

export default createSchema({
  name: 'default',
  types: schemaTypes.concat([
    fullfillment,
    speech,
  ])
})

If you now run sanity start in your command line interface within the project’s root folder, the studio will start up locally, and you’ll be able to add entries for fulfillments. You can keep the studio running while we go on, as it will auto-reload with new changes when you save the files.

Adding SSML To The Editor

By default, the block type will give you a standard editor for visually oriented rich text with heading styles, decorator styles for emphasis and strong, annotations for links, and lists. Now we want to override those with the audial concepts found in SSML.

We begin with defining the different content structures, with helpful descriptions for the editors, that we will add to the block in SSMLeditorSchema.js as configurations for annotations. Those are “emphasis”, “alias”, “prosody”, and “say as”.

Emphasis

We begin with “emphasis”, which controls how much weight is put on the marked text. We define it as a string with a list of predefined values that the user can choose from:

// emphasis.js
export default {
  name: 'emphasis',
  type: 'object',
  title: 'Emphasis',
  description:
    'The strength of the emphasis put on the contained text',
  fields: [
    {
      name: 'level',
      type: 'string',
      options: {
        list: [
          { value: 'strong', title: 'Strong' },
          { value: 'moderate', title: 'Moderate' },
          { value: 'none', title: 'None' },
          { value: 'reduced', title: 'Reduced' }
        ]
      }
    }
  ]
}

Alias

Sometimes the written and the spoken term differ. For instance, you want to use the abbreviation of a phrase in a written text, but have the whole phrase read aloud. For example:

<s>This is a <sub alias="Speech Synthesis Markup Language">SSML</sub> tutorial</s>
Press play to listen to the snippet:

The input field for the alias is a simple string:

// alias.js
export default {
  name: 'alias',
  type: 'object',
  title: 'Alias (sub)',
  description:
    'Replaces the contained text for pronunciation. This allows a document to contain both a spoken and written form.',
  fields: [
    {
      name: 'text',
      type: 'string',
      title: 'Replacement text',
    }
  ]
}

Prosody

With the prosody property we can control different aspects how text should be spoken, like pitch, rate, and volume. The markup for this can look like this:

<s>Say this with an <prosody pitch="x-low">extra low pitch</prosody>, and this <prosody rate="fast" volume="loud">loudly with a fast rate</prosody></s>
Press play to listen to the snippet:

This input will have three fields with predefined string options:

// prosody.js
export default {
  name: 'prosody',
  type: 'object',
  title: 'Prosody',
  description: 'Control of the pitch, speaking rate, and volume',
  fields: [
    {
      name: 'pitch',
      type: 'string',
      title: 'Pitch',
      description: 'The baseline pitch for the contained text',
      options: {
        list: [
          { value: 'x-low', title: 'Extra low' },
          { value: 'low', title: 'Low' },
          { value: 'medium', title: 'Medium' },
          { value: 'high', title: 'High' },
          { value: 'x-high', title: 'Extra high' },
          { value: 'default', title: 'Default' }
        ]
      }
    },
    {
      name: 'rate',
      type: 'string',
      title: 'Rate',
      description:
        'A change in the speaking rate for the contained text',
      options: {
        list: [
          { value: 'x-slow', title: 'Extra slow' },
          { value: 'slow', title: 'Slow' },
          { value: 'medium', title: 'Medium' },
          { value: 'fast', title: 'Fast' },
          { value: 'x-fast', title: 'Extra fast' },
          { value: 'default', title: 'Default' }
        ]
      }
    },
    {
      name: 'volume',
      type: 'string',
      title: 'Volume',
      description: 'The volume for the contained text.',
      options: {
        list: [
          { value: 'silent', title: 'Silent' },
          { value: 'x-soft', title: 'Extra soft' },
          { value: 'medium', title: 'Medium' },
          { value: 'loud', title: 'Loud' },
          { value: 'x-loud', title: 'Extra loud' },
          { value: 'default', title: 'Default' }
        ]
      }
    }
  ]
}

Say As

The last one we want to include is . This tag lets us exercise a bit more control over how certain information is pronounced. We can even use it to bleep out words if you need to redact something in voice interfaces. That’s @!%&© useful!

<s>Do I have to <say-as interpret-as="expletive">frakking</say-as> <say-as interpret-as="verbatim">spell</say-as> it out for you!?</s>
Press play to listen to the snippet:
// sayAs.js
export default {
  name: 'sayAs',
  type: 'object',
  title: 'Say as...',
  description: 'Lets you indicate information about the type of text construct that is contained within the element. It also helps specify the level of detail for rendering
  the contained text.',
  fields: [
    {
      name: 'interpretAs',
      type: 'string',
      title: 'Interpret as...',
      options: {
        list: [
          { value: 'cardinal', title: 'Cardinal numbers' },
          {
            value: 'ordinal',
            title: 'Ordinal numbers (1st, 2nd, 3th...)'
          },
          { value: 'characters', title: 'Spell out characters' },
          { value: 'fraction', title: 'Say numbers as fractions' },
          { value: 'expletive', title: 'Blip out this word' },
          {
            value: 'unit',
            title: 'Adapt unit to singular or plural'
          },
          {
            value: 'verbatim',
            title: 'Spell out letter by letter (verbatim)'
          },
          { value: 'date', title: 'Say as a date' },
          { value: 'telephone', title: 'Say as a telephone number' }
        ]
      }
    },
    {
      name: 'date',
      type: 'object',
      title: 'Date',
      fields: [
        {
          name: 'format',
          type: 'string',
          description: 'The format attribute is a sequence of date field character codes. Supported field character codes in format are {y, m, d} for year, month, and day (of the month) respectively. If the field code appears once for year, month, or day then the number of digits expected are 4, 2, and 2 respectively. If the field code is repeated then the number of expected digits is the number of times the code is repeated. Fields in the date text may be separated by punctuation and/or spaces.'
        },
        {
          name: 'detail',
          type: 'number',
          validation: Rule =>
            Rule.required()
              .min(0)
              .max(2),
          description: 'The detail attribute controls the spoken form of the date. For detail='1' only the day fields and one of month or year fields are required, although both may be supplied'
        }
      ]
    }
  ]
}

Now we can import these in an annotations.js file, which makes things a bit tidier.

// annotations.js
export {default as alias} from './alias'
export {default as emphasis} from './emphasis'
export {default as prosody} from './prosody'
export {default as sayAs} from './sayAs'

Now we can import these annotation types into our main schemas:

// schema.js
import createSchema from "part:@sanity/base/schema-creator"
import schemaTypes from "all:part:@sanity/base/schema-type"
import fulfillment from './fulfillment'
import speech from './ssml-editor/speech'
import {
  alias,
  emphasis,
  prosody,
  sayAs
} from './annotations'

export default createSchema({
  name: "default",
  types: schemaTypes.concat([
    fulfillment,
    speech,
    alias,
    emphasis,
    prosody,
    sayAs
  ])
})

Finally, we can now add these to the editor like this:

// speech.js
export default {
  name: 'speech',
  type: 'array',
  title: 'SSML Editor',
  of: [
    {
      type: 'block',
      styles: [],
      lists: [],
      marks: {
        decorators: [],
        annotations: [
          {type: 'alias'},
          {type: 'emphasis'},
          {type: 'prosody'},
          {type: 'sayAs'}
        ]
      }
    }
  ]
}

Notice that we also added empty arrays to styles, and decorators. This disables the default styles and decorators (like bold and emphasis) since they don’t make that much sense in this specific case.

Customizing The Look And Feel

Now we have the functionality in place, but since we haven’t specified any icons, each annotation will use the default icon, which makes the editor hard to actually use for authors. So let’s fix that!

With the editor for Portable Text it’s possible to inject React components both for the icons and for how the marked text should be rendered. Here, we’ll just let some emoji do the work for us, but you could obviously go far with this, making them dynamic and so on. For prosody we’ll even make the icon change depending on the volume selected. Note that I omitted the fields in these snippets for brevity, you shouldn’t remove them in your local files.

// alias.js
import React from 'react'

export default {
  name: 'alias',
  type: 'object',
  title: 'Alias (sub)',
  description: 'Replaces the contained text for pronunciation. This allows a document to contain both a spoken and written form.',
  fields: [
    /* all the fields */
  ],
  blockEditor: {
    icon: () => '🔤',
    render: ({ children }) => <span>{children} 🔤</span>,
  },
};
// emphasis.js
import React from 'react'

export default {
  name: 'emphasis',
  type: 'object',
  title: 'Emphasis',
  description: 'The strength of the emphasis put on the contained text',
  fields: [
    /* all the fields */
  ],
  blockEditor: {
    icon: () => '🗯',
    render: ({ children }) => <span>{children} 🗯</span>,
  },
};

// prosody.js
import React from 'react'

export default {
  name: 'prosody',
  type: 'object',
  title: 'Prosody',
  description: 'Control of the pitch, speaking rate, and volume',
  fields: [
    /* all the fields */
  ],
  blockEditor: {
    icon: () => '🔊',
    render: ({ children, volume }) => (
      <span>
        {children} {['x-loud', 'loud'].includes(volume) ? '🔊' : '🔈'}
      </span>
    ),
  },
};
// sayAs.js
import React from 'react'

export default {
  name: 'sayAs',
  type: 'object',
  title: 'Say as...',
  description: 'Lets you indicate information about the type of text construct that is contained within the element. It also helps specify the level of detail for rendering the contained text.',
  fields: [
    /* all the fields */
  ],
  blockEditor: {
    icon: () => '🗣',
    render: props => <span>{props.children} 🗣</span>,
  },
};


The customized SSML editor
The editor with our custom SSML marks (Large preview)

Now you have an editor for editing text that can be used by voice assistants. But wouldn’t it be kinda useful if editors also could preview how the text actually will sound like?

Adding A Preview Button Using Google’s Text-to-Speech

Native speech synthesis support is actually on its way for browsers. But in this tutorial, we’ll use Google’s Text-to-Speech API which supports SSML. Building this preview functionality will also be a demonstration of how you serialize Portable Text into SSML in whatever service you want to use this for.

Wrapping The Editor In A React Component

We begin with opening the SSMLeditor.js file and add the following code:

// SSMLeditor.js
import React, { Fragment } from 'react';
import { BlockEditor } from 'part:@sanity/form-builder';

export default function SSMLeditor(props) {
  return (
    <Fragment>
      <BlockEditor {...props} />
    </Fragment>
  );
}

We have now wrapped the editor in our own React component. All the props it needs, including the data it contains, are passed down in real-time. To actually use this component, you have to import it into your speech.js file:

// speech.js
import React from 'react'
import SSMLeditor from './SSMLeditor.js'

export default {
  name: 'speech',
  type: 'array',
  title: 'SSML Editor',
  inputComponent: SSMLeditor,
  of: [
    {
      type: 'block',
      styles: [],
      lists: [],
      marks: {
        decorators: [],
        annotations: [
          { type: 'alias' },
          { type: 'emphasis' },
          { type: 'prosody' },
          { type: 'sayAs' },
        ],
      },
    },
  ],
}

When you save this and the studio reloads, it should look pretty much exactly the same, but that’s because we haven’t started tweaking the editor yet.

Convert Portable Text To SSML

The editor will save the content as Portable Text, an array of objects in JSON that makes it easy to convert rich text into whatever format you need it to be. When you convert Portable Text into another syntax or format, we call that “serialization”. Hence, “serializers” are the recipes for how the rich text should be converted. In this section, we will add serializers for speech synthesis.

You have already made the blocksToSSML.js file. Now we’ll need to add our first dependency. Begin by running the terminal command npm init -y inside the ssml-editor folder. This will add a package.json where the editor’s dependencies will be listed.

Once that’s done, you can run npm install @sanity/block-content-to-html to get a library that makes it easier to serialize Portable Text. We’re using the HTML-library because SSML has the same XML syntax with tags and attributes.

This is a bunch of code, so do feel free to copy-paste it. I’ll explain the pattern right below the snippet:

// blocksToSSML.js
import blocksToHTML, { h } from '@sanity/block-content-to-html'

const serializers = {
  marks: {
    prosody: ({ children, mark: { rate, pitch, volume } }) =>
      h('prosody', { attrs: { rate, pitch, volume } }, children),
    alias: ({ children, mark: { text } }) =>
      h('sub', { attrs: { alias: text } }, children),
    sayAs: ({ children, mark: { interpretAs } }) =>
      h('say-as', { attrs: { 'interpret-as': interpretAs } }, children),
    break: ({ children, mark: { time, strength } }) =>
      h('break', { attrs: { time: '${time}ms', strength } }, children),
    emphasis: ({ children, mark: { level } }) =>
      h('emphasis', { attrs: { level } }, children)
  }
}

export const blocksToSSML = blocks => blocksToHTML({ blocks, serializers })

This code will export a function that takes the array of blocks and loop through them. Whenever a block contains a mark, it will look for a serializer for the type. If you have marked some text to have emphasis, it this function from the serializers object:

emphasis: ({ children, mark: { level } }) =>
      h('emphasis', { attrs: { level } }, children)

Maybe you recognize the parameter from where we defined the schema? The h() function lets us defined an HTML element, that is, here we “cheat” and makes it return an SSML element called . We also give it the attribute level if that is defined, and place the children elements within it — which in most cases will be the text you have marked up with emphasis.

{
    "_type": "block",
    "_key": "f2c4cf1ab4e0",
    "style": "normal",
    "markDefs": [
        {
            "_type": "emphasis",
            "_key": "99b28ed3fa58",
            "level": "strong"
        }
    ],
    "children": [
        {
            "_type": "span",
            "_key": "f2c4cf1ab4e01",
            "text": "Say this strongly!",
            "marks": [
                "99b28ed3fa58"
            ]
        }
    ]
}

That is how the above structure in Portable Text gets serialized to this SSML:

<emphasis level="strong">Say this strongly</emphasis>

If you want support for more SSML tags, you can add more annotations in the schema, and add the annotation types to the marks section in the serializers.

Now we have a function that returns SSML markup from our marked up rich text. The last part is to make a button that lets us send this markup to a text-to-speech service.

Adding A Preview Button That Speaks Back To You

Ideally, we should have used the browser’s speech synthesis capabilities in the Web API. That way, we would have gotten away with less code and dependencies.

As of early 2019, however, native browser support for speech synthesis is still in its early stages. It looks like support for SSML is on the way, and there is proof of concepts of client-side JavaScript implementations for it.

Chances are that you are going to use this content with a voice assistant anyways. Both Google Assistant and Amazon Echo (Alexa) support SSML as responses in a fulfillment. In this tutorial, we will use Google’s text-to-speech API, which also sounds good and support several languages.

Start by obtaining an API key by signing up for Google Cloud Platform (it will be free for the first 1 million characters you process). Once you’re signed up, you can make a new API key on this page.

Now you can open your PreviewButton.js file, and add this code to it:

// PreviewButton.js
import React from 'react'
import Button from 'part:@sanity/components/buttons/default'
import { blocksToSSML } from './blocksToSSML'

// You should be careful with sharing this key
// I put it here to keep the code simple
const API_KEY = '<yourAPIkey>'
const GOOGLE_TEXT_TO_SPEECH_URL = 'https://texttospeech.googleapis.com/v1beta1/text:synthesize?key=' + API_KEY

const speak = async blocks => {
  // Serialize blocks to SSML
  const ssml = blocksToSSML(blocks)
  // Prepare the Google Text-to-Speech configuration
  const body = JSON.stringify({
    input: { ssml },
    // Select the language code and voice name (A-F)
    voice: { languageCode: 'en-US', name: 'en-US-Wavenet-A' },
    // Use MP3 in order to play in browser
    audioConfig: { audioEncoding: 'MP3' }
  })
  // Send the SSML string to the API
  const res = await fetch(GOOGLE_TEXT_TO_SPEECH_URL, {
    method: 'POST',
    body
  }).then(res => res.json())
  // Play the returned audio with the Browser's Audo API
  const audio = new Audio('data:audio/wav;base64,' + res.audioContent)
  audio.play()
}

export default function PreviewButton (props) {
  return <Button style={{ marginTop: '1em' }} onClick={() => speak(props.blocks)}>Speak text</Button>
}

I’ve kept this preview button code to a minimal to make it easier to follow this tutorial. Of course, you could build it out by adding state to show if the preview is processing or make it possible to preview with the different voices that Google’s API supports.

Add the button to SSMLeditor.js:

// SSMLeditor.js
import React, { Fragment } from 'react';
import { BlockEditor } from 'part:@sanity/form-builder';
import PreviewButton from './PreviewButton';

export default function SSMLeditor(props) {
  return (
    <Fragment>
      <BlockEditor {...props} />
      <PreviewButton blocks={props.value} />
    </Fragment>
  );
}

Now you should be able to mark up your text with the different annotations, and hear the result when pushing “Speak text”. Cool, isn’t it?

You’ve Created A Speech Synthesis Editor, And Now What?

If you have followed this tutorial, you have been through how you can use the editor for Portable Text in Sanity Studio to make custom annotations and customize the editor. You can use these skills for all sorts of things, not only to make a speech synthesis editor. You have also been through how to serialize Portable Text into the syntax you need. Obviously, this is also handy if you’re building frontends in React or Vue. You can even use these skills to generate Markdown from Portable Text.

We haven’t covered how you actually use this together with a voice assistant. If you want to try, you can use much of the same logic as with the preview button in a serverless function, and set it as the API endpoint for a fulfillment using webhooks, e.g. with Dialogflow.

If you’d like me to write a tutorial on how to use the speech synthesis editor with a voice assistant, feel free to give me a hint on Twitter or share in the comments section below.

Further Reading on SmashingMag:

(dm, ra, yk, il)
Categories: Others Tags:

Learnability in UX Design

March 21st, 2019 No comments

Building a learnable website is much tougher than it sounds.

One thinks one’s design is clear and comprehensible; however, a design that might be obvious for you, might be perceived totally different by a user with a different set of experiences. Therefore, the goal is to design a clear user path that visitors can quickly pick up and understand.

Why Learnability Matters

Learnability has a strong correlation with usability. It is vital for users to quickly understand the layout and purpose of an application. Especially for web applications, providing an easy to learn interface is important. It is much more convenient to design an easy to understand mobile app compared with a web application; a mobile screen just doesn’t allow to provide a complex interface or let the user accomplish difficult tasks.

The speed of adoption is not the only criteria why learnability matters. A website that looks familiar and provides an understandable interface will result in a lower bounce rate. This is especially useful for websites that try to boost their conversion rate. A complex design scares users and they will resort to other tools that provide a clear interface. In the end, the goal of every website is to convert an occasional user into a repeated user and engage the user for interaction.

Learnability by Example

We can find loads of examples on the internet where learnability has been applied in the right way. Let’s take a look at the key elements of learnability in design…

Small Hints

A few days ago, I moved to Berlin and I had to fill in a form for calculating the cost for my European health insurance. Unfortunately, the form is only available in German, however, due to the great combination of visuals and text, I could perfectly understand what information they required. This is a great example of how an icon can reflect a possible answer.

Other small hints like a tooltip or default text can give a user an initial idea about how the interface can be used and what options are available. Let’s take the Twitter “Compose new Tweet” modal as an example. The design asks the user to tell what is happening. The initial response of a new user would be to input what just happened into the field. Besides that, when the user hovers one of the icons below the text field, a tooltip will appear telling the user what action the icon allows. In short, no space is wasted on adding text, the design speaks for itself.

Familiarity by Consistency

Google uses its own design system (Material Design) which is increasingly used across all of its products. Therefore, a call-to-action button will be the same across tools. Users who have used Gmail should recognize a lot of the elements when using Google Drive for the first time.

This familiarity eases the adoption process of a new interface as users are able to transfer their mental model of one product onto another. Especially for an older generation who didn’t grow up with computers, this familiarity is important as they tend to avoid change and learning new interfaces.

A mental model represents a person’s thought process for how something works. Mental models are based on incomplete facts, past experiences, and even intuitive perceptions. They help shape actions and behavior, influence what people pay attention to in complicated situations and define how people approach and solve new problems or interfaces.

A snippet from Susan Carey’s 1986 journal article about ‘Cognitive science and science education’.

You can find this familiarity also on blogs, but not that explicit as only certain elements are implicitly required. For example, the hamburger icon indicates a menu is hidden and can be unfolded by clicking the hamburger. Mostly, you’ll find a search icon on the right side of the navigation bar.

Also, the layout across blogs is quite consistent. A blog always consists of a header with clear navigation followed by some featured articles and then the body of the article. We, as users, became familiar with this concept so that a blog with a different layout will look, and feel strange to us.

Evidence of Actions

In addition to making sure actions are comprehensible, it’s also important to make sure the user has evidence of their actions; this helps to reinforce what reaction each operation produces throughout the journey.

To give you a simple example, when completing a form, you are shown a ‘thank you’ or a mail that indicates the completion. For a user, that is clear evidence they have used the interface correctly. Why does this matter? Providing feedback during the learning process helps a user to remember the interface better as he immediately learns what is possible or not. Proper feedback mechanisms can reduce the learning curve quickly and also help the user increase his efficiency while using the tool.

To give an example, instead of solely giving feedback upon submission of a form, let’s provide feedback along the way on a field per field basis. This can be as simple as showing a list of requirements for a password field: whenever the password meets one of the listed requirements, the requirement gets ticked off; when all requirements are met, the input field turns green indicating the user can move on to the next input field.

How to Measure Learnability?

Actually, it is not that difficult to design a solid process for measuring learnability. First of all, a key indicator for learnability is the bounce rate. Therefore, using Google Analytics is crucial to gain insights.

Besides Google Analytics, you can perform tests yourself with random test subjects: Provide an interface to your test subjects and give them five simple tasks to complete. For example, you have an online platform for creating and sending invoices, let your users perform the following tasks:

  • Create an invoice with one item;
  • Edit invoice;
  • Send invoice to receiver;
  • Track payment for the invoice;
  • Download completed invoice for personal bookkeeping.

Now, let your test subjects perform these simple tasks five times in a row with each time a day of rest. It is important to measure the time needed for completing each task. A design that provides good learnability capabilities should see an increase in efficiency while repeating tasks. After five repetitions it is normal to see stagnation as the user has reached the limits of efficiency (unless they are superhuman).

Many elements determine the learnability factor. Never take your design for granted, there is always room for optimization. Use these tips in practice and see how you can optimize the conversion rate of your design.

Featured image via Unsplash

Add Realistic Chalk and Sketch Lettering Effects with Sketch’it – only $5!

Source

Categories: Designing, Others Tags:

Blurred Borders in CSS

March 20th, 2019 No comments
Screenshot of an element with a background image that shows oranges on a wooden table. The border of this element is blurred.

Say we want to target an element and just visually blur the border of it. There is no simple, single built-in web platform feature we can reach for. But we can get it done with a little CSS trickery.

Here’s what we’re after:

The desired result.

Let’s see how we can code this effect, how we can enhance it with rounded corners, extend support so it works cross-browser, what the future will bring in this department and what other interesting results we can get starting from the same idea!

Coding the basic blurred border

We start with an element on which we set some dummy dimensions, a partially transparent (just slightly visible) border and a background whose size is relative to the border-box, but whose visibility we restrict to the padding-box:

$b: 1.5em; // border-width

div {
  border: solid $b rgba(#000, .2);
  height: 50vmin;
  max-width: 13em;
  max-height: 7em;
  background: url(oranges.jpg) 50%/ cover 
                border-box /* background-origin */
                padding-box /* background-clip */;
}

The box specified by background-origin is the box whose top left corner is the 0 0 point for background-position and also the box that background-size (set to cover in our case) is relative to. The box specified by background-clip is the box within whose limits the background is visible.

The initial values are padding-box for background-origin and border-box for background-clip, so we need to specify them both in this case.

If you need a more in-depth refresher on background-origin and background-clip, you can check out this detailed article on the topic.

The code above gives us the following result:

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we add an absolutely positioned pseudo-element that covers its entire parent’s border-box and is positioned behind (z-index: -1). We also make this pseudo-element inherit its parent’s border and background, then we change the border-color to transparent and the background-clip to border-box:

$b: 1.5em; // border-width

div {
  position: relative;
  /* same styles as before */
  
  &:before {
    position: absolute;
    z-index: -1;
    /* go outside padding-box by 
     * a border-width ($b) in every direction */
    top: -$b; right: -$b; bottom: -$b; left: -$b;
    border: inherit;
    border-color: transparent;
    background: inherit;
    background-clip: border-box;
    content: ''
  }
}

Now we can also see the background behind the barely visible border:

See the Pen by thebabydino (@thebabydino) on CodePen.

Alright, you may be seeing already where this is going! The next step is to blur() the pseudo-element. Since this pseudo-element is only visible only underneath the partially transparent border (the rest is covered by its parent’s padding-box-restricted background), it results the border area is the only area of the image we see blurred.

See the Pen by thebabydino (@thebabydino) on CodePen.

We’ve also brought the alpha of the element’s border-color down to .03 because we want the blurriness to be doing most of the job of highlighting where the border is.

This may look done, but there’s something I still don’t like: the edges of the pseudo-element are now blurred as well. So let’s fix that!

One convenient thing when it comes to the order browsers apply properties in is that filters are applied before clipping. While this is not what we want and makes us resort to inconvenient workarounds in a lot of other cases… right here, it proves to be really useful!

It means that, after blurring the pseudo-element, we can clip it to its border-box!

My preferred way of doing this is by setting clip-path to inset(0) because… it’s the simplest way of doing it, really! polygon(0 0, 100% 0, 100% 100%, 0 100%) would be overkill.

See the Pen by thebabydino (@thebabydino) on CodePen.

In case you’re wondering why not set the clip-path on the actual element instead of setting it on the :before pseudo-element, this is because setting clip-path on the element would make it a stacking context. This would force all its child elements (and consequently, its blurred :before pseudo-element as well) to be contained within it and, therefore, in front of its background. And then no nuclear z-index or !important could change that.

We can prettify this by adding some text with a nicer font, a box-shadow and some layout properties.

What if we have rounded corners?

The best thing about using inset() instead of polygon() for the clip-path is that inset() can also accommodate for any border-radius we may want!

And when I say any border-radius, I mean it! Check this out!

div {
  --r: 15% 75px 35vh 13vw/ 3em 5rem 29vmin 12.5vmax;
  border-radius: var(--r);
  /* same styles as before */
  
  &:before {
    /* same styles as before */
    border-radius: inherit;
    clip-path: inset(0 round var(--r));
  }
}

It works like a charm!

See the Pen by thebabydino (@thebabydino) on CodePen.

Extending support

Some mobile browsers still need the -webkit- prefix for both filter and clip-path, so be sure to include those versions too. Note that they are included in the CodePen demos embeded here, even though I chose to skip them in the code presented in the body of this article.

Alright, but what if we need to support Edge? clip-path doesn’t work in Edge, but filter does, which means we do get the blurred border, but no sharp cut limits.

Well, if we don’t need corner rounding, we can use the deprecated clip property as a fallback. This means adding the following line right before the clip-path ones:

clip: rect(0 100% 100% 0)

And our demo now works in Edge… sort of! The right, bottom and left edges are cut sharply, but the top one still remains blurred (only in the Debug mode of the Pen, all seems fine for the iframe in the Editor View). And opening DevTools or right clicking in the Edge window or clicking anywhere outside this window makes the effect of this property vanish. Bug of the month right there!

Alright, since this is so unreliable and it doesn’t even help us if we want rounded corners, let’s try another approach!

This is a bit like scratching behind the left ear with the right foot (or the other way around, depending on which side is your more flexible one), but it’s the only way I can think of to make it work in Edge.

Some of you may have already been screaming at the screen something like “but Ana… overflow: hidden!” and yes, that’s what we’re going for now. I’ve avoided it initially because of the way it works: it cuts out all descendant content outside the padding-box. Not outside the border-box, as we’ve done by clipping!

This means we need to ditch the real border and emulate it with padding, which I’m not exactly delighted about because it can lead to more complications, but let’s take it one step at a time!

As far as code changes are concerned, the first thing we do is remove all border-related properties and set the border-width value as the padding. We then set overflow: hidden and restrict the background of the actual element to the content-box. Finally, we reset the pseudo-element’s background-clip to the padding-box value and zero its offsets.

$fake-b: 1.5em; // fake border-width

div {
  /* same styles as before */
  overflow: hidden;
  padding: $fake-b;
  background: url(oranges.jpg) 50%/ cover 
                padding-box /* background-origin */
                content-box /* background-clip */;
  
  &:before {
    /* same styles as before */
    top: 0; right: 0; bottom: 0; left: 0;
    background: inherit;
    background-clip: padding-box;
  }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

If we want that barely visible “border” overlay, we need another background layer on the actual element:

$fake-b: 1.5em; // fake border-width
$c: rgba(#000, .03);

div {
  /* same styles as before */
  overflow: hidden;
  padding: $fake-b;
  --img: url(oranges.jpg) 50%/ cover;
  background: var(--img)
                padding-box /* background-origin */
                content-box /* background-clip */,  
              linear-gradient($c, $c);
  
  &:before {
    /* same styles as before */
    top: 0; right: 0; bottom: 0; left: 0;
    background: var(--img);
  }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

We can also add rounded corners with no hassle:

See the Pen by thebabydino (@thebabydino) on CodePen.

So why didn’t we do this from the very beginning?!

Remember when I said a bit earlier that not using an actual border can complicate things later on?

Well, let’s say we want to have some text. With the first method, using an actual border and clip-path, all it takes to prevent the text content from touching the blurred border is adding a padding (of let’s say 1em) on our element.

See the Pen by thebabydino (@thebabydino) on CodePen.

But with the overflow: hidden method, we’ve already used the padding property to create the blurred “border”. Increasing its value doesn’t help because it only increases the fake border’s width.

We could add the text into a child element. Or we could also use the :after pseudo-element!

The way this works is pretty similar to the first method, with the :after replacing the actual element. The difference is we clip the blurred edges with overflow: hidden instead of clip-path: inset(0) and the padding on the actual element is the pseudos’ border-width ($b) plus whatever padding value we want:

$b: 1.5em; // border-width

div {
  overflow: hidden;
  position: relative;
  padding: calc(1em + #{$b});
  /* prettifying styles */
	
  &:before, &:after {
    position: absolute;
    z-index: -1; /* put them *behind* parent */
    /* zero all offsets */
    top: 0; right: 0; bottom: 0; left: 0;
    border: solid $b rgba(#000, .03);
    background: url(oranges.jpg) 50%/ cover 
                  border-box /* background-origin */
                  padding-box /* background-clip */;
    content: ''
  }
	
  &:before {
    border-color: transparent;
    background-clip: border-box;
    filter: blur(9px);
  }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

What about having both text and some pretty extreme rounded corners? Well, that’s something we’ll discuss in another article – stay tuned!

What about backdrop-filter?

Some of you may be wondering (as I was when I started toying with various ideas in order to try to achieve this effect) whether backdrop-filter isn’t an option.

Well, yes and no!

Technically, it is possible to get the same effect, but since Firefox doesn’t yet implement it, we’re cutting out Firefox support if we choose to take this route. Not to mention this approach also forces us to use both pseudo-elements if we want the best support possible for the case when our element has some text content (which means we need the pseudos and their padding-box area background to show underneath this text).

For those who don’t yet know what backdrop-filter does: it filters out what can be seen through the (partially) transparent parts of the element we apply it on.

The way we need to go about this is the following: both pseudo-elements have a transparent border and a background positioned and sized relative to the padding-box. We restrict the background of pseudo-element on top (the :after) to the padding-box.

Now the :after doesn’t have a background in the border area anymore and we can see through to the :before pseudo-element behind it there. We set a backdrop-filter on the :after and maybe even change that border-color from transparent to slightly visible. The bottom (:before) pseudo-element’s background that’s still visible through the (partially) transparent, barely distinguishable border of the :after above gets blurred as a result of applying the backdrop-filter.

$b: 1.5em; // border-width

div {
  overflow: hidden;
  position: relative;
  padding: calc(1em + #{$b});
  /* prettifying styles */
	
  &:before, &:after {
    position: absolute;
    z-index: -1; /* put them *behind* parent */
    /* zero all offsets */
    top: 0; right: 0; bottom: 0; left: 0;
    border: solid $b transparent;
    background: $url 50%/ cover 
                  /* background-origin & -clip */
                  border-box;
    content: ''
  }
	
  &:after {
    border-color: rgba(#000, .03);
    background-clip: padding-box;
    backdrop-filter: blur(9px); /* no Firefox support */
  }
}

Remember that the live demo for this doesn’t currently work in Firefox and needs the Experimental Web Platform features flag enabled in chrome://flags in order to work in Chrome.

Eliminating one pseudo-element

This is something I wouldn’t recommend doing in the wild because it cuts out Edge support as well, but we do have a way of achieving the result we want with just one pseudo-element.

We start by setting the image background on the element (we don’t really need to explicitly set a border as long as we include its width in the padding) and then a partially transparent, barely visible background on the absolutely positioned pseudo-element that’s covering its entire parent. We also set the backdrop-filter on this pseudo-element.

$b: 1.5em; // border-width

div {
  position: relative;
  padding: calc(1em + #{$b});
  background: url(oranges.jpg) 50%/ cover;
  /* prettifying styles */
	
  &:before {
    position: absolute;
    /* zero all offsets */
    top: 0; right: 0; bottom: 0; left: 0;
    background: rgba(#000, .03);
    backdrop-filter: blur(9px); /* no Firefox support */
    content: ''
  }
}

Alright, but this blurs out the entire element behind the almost transparent pseudo-element, including its text. And it’s no bug, this is what backdrop-filter is supposed to do.

Screenshot.
The problem at hand.

In order to fix this, we need to get rid of (not make transparent, that’s completely useless in this case) the inner rectangle (whose edges are a distance $b away from the border-box edges) of the pseudo-element.

We have two ways of doing this.

The first way (live demo) is with clip-path and the zero-width tunnel technique:

$b: 1.5em; // border-width
$o: calc(100% - #{$b});

div {
  /* same styles as before */
	
  &:before {
    /* same styles as before */

    /* doesn't work in Edge */
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 
                       0 0, 
                       #{$b $b}, #{$b $o}, #{$o $o}, #{$o $b}, 
                       #{$b $b});
  }
}

The second way (live demo) is with two composited mask layers (note that, in this case, we need to explicitly set a border on our pseudo):

$b: 1.5em; // border-width

div {
  /* same styles as before */
	
  &:before {
    /* same styles as before */

    border: solid $b transparent;

    /* doesn't work in Edge */
    --fill: linear-gradient(red, red);
    -webkit-mask: var(--fill) padding-box, 
                  var(--fill);
    -webkit-mask-composite: xor;
            mask: var(--fill) padding-box exclude, 
                  var(--fill);
  }
}

Since neither of these two properties works in Edge, this means support is now limited to WebKit browsers (and we still need to enable the Experimental Web Platform features flag for backdrop-filter to work in Chrome).

Future (and better!) solution

This is not implemented by any browser at this point, but the spec mentions a filter() function that will allow us to apply filters on individual background layers. This would eliminate the need for a pseudo-element and would reduce the code needed to achieve this effect to two CSS declarations!

border: solid 1.5em rgba(#000, .03);
background: $url 
              border-box /* background-origin */
              padding-box /* background-clip */, 
            filter($url, blur(9px)) 
              /* background-origin & background-clip */
              border-box

If you think this is something useful to have, you can add your use cases and track implementation progress for both Chrome and Firefox.

More border filter options

I’ve only talked about blurring the border up to now, but this technique works for pretty much any CSS filter (save for drop-shadow() which wouldn’t make much sense in this context). You can play with switching between them and tweaking values in the interactive demo below:

See the Pen by thebabydino (@thebabydino) on CodePen.

And all we’ve done so far has used just one filter function, but we can also chain them and then the possibilities are endless – what cool effects can you come up with this way?

See the Pen by thebabydino (@thebabydino) on CodePen.

The post Blurred Borders in CSS appeared first on CSS-Tricks.

Categories: Designing, Others Tags: