You are here
CSS Tricks
Freak Flags
I don't see image sprites used that much anymore, but it's still a good technique for reducing downloaded decorative image assets when you have multiple on a page. The big idea is combining all the graphics into one and then shifting around the size and background-position to reveal one at a time.
Say you're site needs dozens or hundreds of country flags — that's a perfect opportunity for a sprite. Michael P. Cohen has built a generator site to help build just what you need.
My first thoughts was... why not SVG? Michael says:
Why not use SVG? Many flags adapt very well to this format. I'm a fan of SVG but it wasn't the right tool for this. The problem is too many flags have a lot of detail in crests and icons and imagery, and those details quickly increase svg file sizes far more than they do for index color files.
Direct Link to Article — Permalink
The post Freak Flags appeared first on CSS-Tricks.
Just Sharing My Gulpfile
Seemingly out of the blue, the Gulp processing I had set up for this site started to have a race condition. I'd run my watch command, change some CSS, and the processing would sometimes leave behind some extra files that were meant to be cleaned up during the processing. Like the cleanup tasks happened before the files landed in the file system (or something... I never really got to the bottom of it).
Nevermind about the specifics of that bug. I figured I'd go about solving it by upgrading things to use Gulp 4.x instead of 3.x, and running things in the built-in gulp.series command, which I thought would help (it did).
Getting Gulp 4.x going was a heck of a journey for me, involving me giving up for a year, then reigniting the struggle and ultimately getting it fixed. My trouble was that Gulp 4 requires a CLI version of 2.x while Gulp 3, for whatever reason, used a 3.x version. Essentially I needed to downgrade versions, but after trying a billion things to do that, nothing seemed to work, like there was a ghost version of CLI 3.x on my machine.
I'm sure savvier command-line folks could have sussed this out faster than me, but it turns out running command -v gulp will reveal the file path of where Gulp is installed which revealed /usr/local/share/npm/bin/gulp for me, and deleting it manually from there before re-installing the lastest version worked in getting me back down to 2.x.
Now that I could use Gulp 4.x, I re-wrote my gulpfile.js into smaller functions, each fairly isolated in responsibility. Much of this is pretty unique to my setup on this site, so it's not meant to be some boilerplate for generic usage. I'm just publishing because it certainly would have been helpful for me to reference as I was creating it.
Things my particular Gulpfile does- Runs a web server (Browsersync) for style injection and auto-refreshing
- Runs a file watcher (native Gulp feature) for running the right tasks on the right files and doing the above things
- CSS processing
- Sass > Autoprefixer > Minify
- Break stylesheet cache from the templates (e.g. <link href="style.css?BREAK_CACHE">
- Put style.css in the right place for a WordPress theme and clean up files only needed during processing
- JavaScript processing
- Babel > Concatenate > Minify
- Break browser cache for the <script>s
- Clean up unused files created in processing
- SVG processing
- Make an SVG sprite (a block of <symbol>s
- Name it as a sprite.php file (so it can be PHP-included in a template) and put it somewhere specific
- PHP processing
- Update the Ajax call in the JavaScript to cache-bust when the ads change
- The worst part is that it doesn't break cache very intelligently. When CSS changes, it breaks the cache on all stylesheets, not just the relevant ones.
- I'd probably just inline SVG icons with PHP include()s in the future rather than deal with spriting.
- The SVG processor breaks if the original SVGs have width and height attributes, which seems wrong.
- Would gulp-changed be a speed boost? As in, only looking at files that have changed instead of all files? Or is it not necessary anymore?
- Should I be restarting gulp on gulpfile.js changes?
- Sure would be nice if all the libs I used were ES6-compatible so I could import stuff rather than having to manually concatenate.
Always so much more that can be done. Ideally, I'd just open-source this whole site, I just haven't gotten there yet.
The post Just Sharing My Gulpfile appeared first on CSS-Tricks.
Making a Better Custom Select Element
We just covered The Current State of Styling Selects in 2019, but we didn't get nearly as far and fancy as Julie Grundy gets here. There is a decent chunk of JavaScript that powers it, so I'm still very much eyeballing browsers' recent interest in giving us more powerful selects in (presumably) just HTML and CSS.
I tossed a fork on CodePen in case you just wanna see the final result.
This is also the first article in the 2019 edition of 24 Ways, the long-running and wonderful annual advent calendar for developers that is worth reading every single year.
Direct Link to Article — Permalink
The post Making a Better Custom Select Element appeared first on CSS-Tricks.
WordPress.com: One CMS, Infinite Possibilities
(This is a sponsored post.)
Have you ever looked at a site and knew exactly what CMS powers it? You might see a distinctive design aesthetic that gives it away. Or maybe it's something even less obvious and even harder to articulate, but you know it when you see it.
That seems true with just about any platform, especially those that rely on a set of templates. If you were to jump from one site ot another on the same platform, you can see the similarities, sort of like walking down the street of a neighborhood where all the homes are designed by the same architect.
It's not a bad thing. But like homes, we tend to want websites with personality and that feel unique. That's one of the things that makes WordPress.com a nice hosted platform option.
Yes, it has core themes, some of which are commonly used. What it also has is hundreds of others, including 110 themes that are free. The designs range from portfolio- and business-themed sites to ones themed around traditional blogs, weddings, travel, music, and food. There's so many to choose from, and they're introducing more every year. Take a look through some live sites using WordPress.com. The variety is awesome and showcases the many possibilities of WordPress as a content management system.
We've said it before: if you can build a site with WordPress.com, you should build a site on WordPress.com. We're proud to have WordPress as a sponsor here at CSS-Tricks and wouldn't hesitate to recommend it to anyone who needs a quick and easy way to spin up a site. Plus, with a free plan tier, it's even easier to get started.
Direct Link to Article — Permalink
The post WordPress.com: One CMS, Infinite Possibilities appeared first on CSS-Tricks.
Quoting in HTML: Quotations, Citations, and Blockquotes
Itâs all too common to see the incorrect HTML used for quotes in markup. In this article, letâs dig into all this, looking at different situations and different HTML tags to handle those situations.
There are three major HTML elements involved in quotations:
- <blockquote>
- <q>
- <cite>
Letâs take a look.
BlockquotesBlockquote tags are used for distinguishing quoted text from the rest of the content. My tenth grade English teacher drilled it into my head that any quote of four lines or longer should be set apart this way. The HTML spec has no such requirement, but as long as the text is a quote and you want it to be set apart from the surrounding text and tags, a blockquote is the semantic choice.
By default, browsers indent blockquotes by adding margin on each side.
See the Pen
The Blockquote Tag by Undead Institute (@undeadinstitute)
on CodePen.
As a flow element (i.e. âblock levelâ element), blockquote can contain other elements inside it. For example, we can drop paragraphs in there with no problem:
<blockquote> <p></p> <p></p> </blockquote>But it could be other elements, too, like a heading or an unordered list:
<blockquote> <h2></h2> <ul> <li></li> <li></li> </ul> </blockquote>Itâs important to note that blockquotes should only be used for quotations rather than as a decorative element in a design. This also aids accessibility as screen reader users can jump between blockquotes. Thus a blockquote element used solely for aesthetics could really confuse those users. If you need something more decorative that falls outside the bounds of extended quotations, then perhaps a div with a class is the way to go.
blockquote, .callout-block { /* These could share styling */ } Quoting with QQ tags (<q>) are for inline quotes, or what my tenth grade teacher would say are those under four lines. Many modern browsers will automatically add quotation marks to the quote as pseudo elements but you may need a backup solution for older browsers.
See the Pen
The Q Tag by CSS-Tricks (@css-tricks)
on CodePen.
Typical quotation marks are just as valid for inline quotes as the <q> element. The benefits of using <q>, however, are that it includes a cite attribute, automatic handling of quotation marks, and automatic handling of quote levels. <q> elements should not used for sarcasm (e.g. âyou would use a <q> tag for sarcasm, wouldnât you?â), or signifying a word with air quotes (e.g. âawesomeâ is an âaccurateâ description of the author). But if you can figure out how to mark up air quotes, please let me know. Because that would be âawesome.â
The citation attributeBoth <q> and blockquotes can use a citation (cite) attribute. This attribute holds a URL that provides context and/or a reference for the quoted material. The spec makes a point of saying that the URL can be surrounded by spaces. (Iâm not sure why thatâs pointed out, but if you want to anger the semantic code deities, youâll have to do more than throw spaces around.)
<p>The officer left a note saying <q cite="https://johnrhea.com/summons">You have been summoned to appear on the 4th day of January on charges of attempted reader bribery.</q></p>That cite attribute isnât visible to the user by default. You could add it in with a sprinkle of CSS magic like the following demo. You could even fiddle with it further to make the citation appear on hover.
See the Pen
Attributable citations by CSS-Tricks (@css-tricks)
on CodePen.
Neither of those options are particularly great. If you need to cite a source such that users can see it and go to it, you should do it in HTML and probably with the <cite> element, which weâll cover next.
The citation elementThe <cite> tag should be used for referencing creative work rather than the person who said or wrote the quote. In other words, itâs not for quotes. Here are the examples from the spec:
<p>My favorite book is <cite>The Reality Dysfunction</cite> by Peter F. Hamilton. My favorite comic is <cite>Pearls Before Swine</cite> by Stephan Pastis. My favorite track is <cite>Jive Samba</cite> by the Cannonball Adderley Sextet.</p>Hereâs another example:
See the Pen
Cite This! by CSS-Tricks (@css-tricks)
on CodePen.
If the author of this article told you heâd give you a cupcake, and you <cite> him by name, that would be semantically incorrect. Thus no cupcakes would change hands. If you cited the article in which he offered you a cupcake, that would be semantically correct, but since the author wouldnât do that, you still wouldnât get a cupcake. Sorry.
By default, browsers italicize cite tags and thereâs no requirement that a <q> or <blockquote> be present to use the cite element. If you want to cite a book or other creative work, then slap it in the cite element. The semantic deities will smile on you for not using either <i> or <em> elements.
But where to put the cite element? Inside? Outside? The upside down? If we put it inside the <blockquote> or the <q>, weâre making it part of the quote. That's forbidden by the spec for just that reason.
<!-- This is apparently wrong --> <blockquote> Quote about cupcake distribution from an article <cite>The Article</cite> </blockquote>Putting it outside just feels wrong and also requires you to have an enclosing element like a <div> if you wanted to style them together.
<div class="need-to-style-together"> <blockquote> Quote about cupcake distribution from an article </blockquote> <cite>The Article</cite> </div>N.B. If you google this issue you may come across an HTML5 Doctor article from 2013 that contradicts much of what's laid out here. That said, every time it links to the docs for support, the docs agree with the article you're currently reading rather than the HTML5 Doctor article. Most likely the docs have changed since that article was written.
Hey, what about the figure element?One way to mark up a quotation â and in a way that pleases the semantic code deities â is to put the blockquote within a figure element. Then, put the cite element and any other author or citation information in a figcaption.
<figure class="quote"> <blockquote> But web browsers arenât like web servers. If your back-end code is getting so big that itâs starting to run noticably slowly, you can throw more computing power at it by scaling up your server. Thatâs not an option on the front-end where you donât really have one run-time environmentâyour end users have their own run-time environment with its own constraints around computing power and network connectivity. </blockquote> <figcaption> — Jeremy Keith, <cite>Mental models</cite> </figcaption> </figure>While this doubles the number of elements needed, there are several benefits:
- Itâs semantically correct for all four elements.
- It allows you to both include and encapsulate author information beyond citing the name of the work.
- It gives you an easy way to style the quote without resorting to divs, spans or wretchedness.
See the Pen
It Figures You'd Say That by CSS-Tricks (@css-tricks)
on CodePen.
Not <dialog>! Those are for attention-grabbing modals. Dialogue, as in, conversational exchanges between people speaking or typing to each other.
Neither <blockquote> nor <q> nor <cite> are to be used for dialogue and similar exchanges between speakers. If youâre marking up dialogue, you can use whatever makes the most sense to you. Thereâs no semantic way to do it. That said, the spec suggests <p> tags and punctuation with <span> or <b> tags to designate the speaker and <i> tags to mark stage directions.
Accessibility of quotesFrom the research Iâve done, screen readers should not have any issue with understanding semantic-deity-approved <q>, <blockquote>, or <cite> tags.
[VIDEO]
More âwaysâ to âquoteâYou can add quotation marks to a <blockquote> using CSS pseudo elements. The <q> element comes with quotation marks baked in so they need not be added, however adding them as pseudo-elements can be a workaround for older browsers that donât automatically add them. Since this is how modern browsers add the quotation marks there's no danger of adding duplicate quotes. New browsers will overwrite the default pseudo elements, and older browsers that support pseudo elements will add the quotes.
But you canât, like I did, assume that the above will always give you smart opening and closing quotes. Even if the font supports smart quotes, sometimes straight quotes will be displayed. To be safe, itâs better to use the quotes CSS property to up the intelligence on those quotation marks.
blockquote { quotes: "â" "â" "â" "â"; }
See the Pen
"Quot-a-tizing" the blockquote by CSS-Tricks (@css-tricks)
on CodePen.
Now letâs look at quote levels. The <q> tag will automatically adjust quote levels.
Letâs say youâre in an area that uses the British convention of using single quotes. You could use the CSS quotes rule to put the opening and closing single quotes first in the list. Hereâs an example of both ways:
See the Pen
Quote Within a Quote by CSS-Tricks (@css-tricks)
on CodePen.
There is no limit to nesting. Those nested <q> elements could even be within a blockquote thatâs within a blockquote.
If you add quotation marks to a blockquote, know that the blockquote does not change the quote level the way a <q> tag does. If you expect to have quotes within a blockquote, you may want to add a descendant selector rule to start <q> elements within a blockquote at the single quote level (or double quotes if you follow British conventions).
blockquote q { quotes: "â" "â" "â" "â"; }The last quote level you put in will continue through subsequent levels of quotation. To use the double, single, double, single⊠convention, add more levels to the CSS quotes property.
q { quotes: "â" "â" "â" "â" "â" "â" "â" "â" "â" "â"; } Hanging punctuationMany typography experts will tell you that hanging the quotation marks on blockquotes looks better (and theyâre right). Hanging punctuation is, in this case, quotation marks that are pushed out from the text so that the characters of the text line up vertically.
One possibility in CSS is using a slightly negative value on the text-indent property. The exact negative indentation will vary by font, so be sure to double check the spacing with the font you end up using.
blockquote { text-indent: -0.45em; }There is a nicer way to handle this by using the hanging-punctuation CSS property. Itâs only supported in Safari at the time of this writing, so weâll have to progressively enhance:
/* Fallback */ blockquote { text-indent: -0.45em; } /* If there's support, erase the indent and use the property instead */ @supports ( hanging-punctuation: first) { blockquote { text-indent: 0; hanging-punctuation: first; } }Using hanging-punctuation is better because itâs less fiddly. Itâll just work when it can.
See the Pen
Hanging Your Punctuation by CSS-Tricks (@css-tricks)
on CodePen.
Of course we can.
See the Pen
Dancing Quotes by CSS-Tricks (@css-tricks)
on CodePen.
Why youâd need to do this, Iâm not totally sure, but the quotation marks in a <q> tag are added are pseudo elements in the UA stylesheet, so weâre able to select and style them â including animation â if we need to.
Wait, maybe we just solved the air quotes thing after all.
The post Quoting in HTML: Quotations, Citations, and Blockquotes appeared first on CSS-Tricks.
How Facebook Avoids Ad Blockers
Facebook actually hides 'dummy' DOM nodes between the 'Sponsored' text. These values are entirely random characters, with a random number of DOM nodes between them. Invisible characters. At this point our CSS ad blocker is completely broken. There is no way for us to possibly code every possible value in CSS.
We've covered this before when Mike Pan noted it. Looks like it's evolved a bit since then, getting even a little tricker.
I just opened my Facebook and selected "Copy Outer HTML" on the word "Sponsored":
<span class="v_19dt4zixpg r_19dt4zk7i5"><span class="fsm fwn fcg"><span class="q_19dt4zirbc"><a class="d_19dt4zioka h_19dt4ziol1" role="button" id="u_fetchstream_3_6"><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="a" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="t" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="S" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="p" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="r" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="i" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="n" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="S" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="i" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="p" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="o" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="i" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="n" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="o" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="a" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="c" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="s" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="n" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="s" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="o" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="r" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="e" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="o" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="g" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="r" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="d" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="e" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="f" class="s_19dt4ziok9 l_19dt4zlqyi b_19dt4ziokl"></span></span><span class="s_19dt4ziok9 d_19dt4ziok- e_19dt4ziokq"><span data-content="d" class="s_19dt4ziok9 l_19dt4zlqyi n_19dt4ziokm"></span></span></span></a></span></span></span>I guess we shouldn't be terribly surprised at Facebook being user-hostile. I can imagine a workplace environment where fighting against ad blockers is turned into this fun kinda cat-and-mouse technological tennis match. But what they are fighting against is people wanting to exert a little control over what they allow into their eyes, ears, and brains.
It's worth noting that nothing else in the DOM helps identify a post as an ad. So in that sense it's just like how Google has evolved SERPs in how ads look just like organic results aside from a tiny "AD" before the URL.
We run sponsored posts here on CSS-Tricks too, so please feel free to hold our feet to the fire of accountability if you feel sponsored posts aren't clear enough.
Direct Link to Article — Permalink
The post How Facebook Avoids Ad Blockers appeared first on CSS-Tricks.
Music and Web Design
Brad has a long history in music outside of being a web designer, and draws some interesting parallels. One is that he had reached for more complex music in an effort to become a better musician — and developers can do the same thing. The other is that the composition of music can be seen from very large parts down to very tiny parts, just like atomic design.
I have two go-to connections between music and web design myself.
- Expectation of practice. Learning to play a musical instrument has a healthy cultural expectation: to get good at it, you have to practice. I'd love to see that expectation make its way more clearly into absolutely everything else in the world, from ironing clothes to inventing soups to writing code.
- Learning from multiple angles. Everybody who is actually good at a thing has learned it from 100 angles. You read books, you practice, you watch YouTube videos, you take a lesson, you go to school, you go out and live it, you get a mentor, you squeeze yourself into social circles. This is true for music (nobody learns from a single source) and equally true for web design (getting good comes from a breadth of experiences).
Allow me to quote myself:
People are obsessed with asking musicians if theyâre self-taught. Like, if they are, their amazingness triples because it means their creative genius was delivered by a lightning bolt at birth. They don't need anyone else to learn; they merely look at those guitar strings and know what to do.
And if they were taught by a teacher, then, well, that's all out the door. If they are good at all, then it's because the teacher delivered that to them.
Total nonsense.
People learn anything â music and web development included â inside a hurricane of influences. Letâs stick with music for a second. Learning to play comes in many forms. You learn by listening to music an awful lot. You can do fundamental practice, like finger exercises and going up and down scales. You can learn to transpose chords on a chalkboard. You can watch YouTube all day and night. You can sign up for online courses. You can go to local jams to watch and play along. You can join a band. You can take lessons from someone advertising on Craigslist. You can go to a local music school. You can read books about music.
You get the idea.
You can and probably will do all of that. With learning web design and development, getting anywhere will involve all sorts of ways. Thereâs no silver bullet. It takes bashing on it lots of different ways. Thereâs no requirement to sprinkle money on it, but you do need multiple angles, time, and motivation.
Direct Link to Article — Permalink
The post Music and Web Design appeared first on CSS-Tricks.
A Handy Sass-Powered Tool for Making Balanced Color Palettes
For those who may not come from a design background, selecting a color palette is often based on personal preferences. Choosing colors might be done with an online color tool, sampling from an image, "borrowing" from favorite brands, or just sort of randomly picking from a color wheel until a palette "just feels right."
Our goal is to better understand what makes a palette "feel right" by exploring key color attributes with Sass color functions. By the end, you will become more familiar with:
- The value of graphing a paletteâs luminance, lightness, and saturation to assist in building balanced palettes
- The importance of building accessible contrast checking into your tools
- Advanced Sass functions to extend for your own explorations, including a CodePen you can manipulate and fork
What youâll ultimately find, however, is that color on the web is a battle of hardware versus human perception.
What makes color graphing usefulYou may be familiar with ways of declaring colors in stylesheets, such as RGB and RGBA values, HSL and HSLA values, and HEX codes.
rbg(102,51,153) rbga(102,51,153, 0.6) hsl(270, 50%, 40%) hsla(270, 50%, 40%, 0.6) #663399Those values give devices instructions on how to render color. Deeper attributes of a color can be exposed programmatically and leveraged to understand how a color relates to a broader palette.
The value of graphing color attributes is that we get a more complete picture of the relationship between colors. This reveals why a collection of colors may or may not feel right together. Graphing multiple color attributes helps hint at what adjustments can be made to create a more harmonious palette. Weâll look into examples of how to determine what to change in a later section.
Two useful measurements we can readily obtain using built-in Sass color functions are lightness and saturation.
- Lightness refers to the mix of white or black with the color.
- Saturation refers to the intensity of a color, with 100% saturation resulting in the purest color (no grey present).
However, luminance may arguably be the most useful color attribute. Luminance, as represented in our tool, is calculated using the WCAG formula which assumes an sRGB color space. Luminance is used in the contrast calculations, and as a grander concept, also aims to get closer to quantifying the human perception of relative brightness to assess color relationships. This means that a tighter luminance value range among a palette is likely to be perceived as more balanced to the human eye. But machines are fallible, and there are exceptions to this rule that you may encounter as you manipulate palette values. For more extensive information on luminance, and a unique color space called CIELAB that aims to even more accurately represent the human perception of color uniformity, see the links at the end of this article.
Additionally, color contrast is exceptionally important for accessibility, particularly in terms of legibility and distinguishing UI elements, which can be calculated programmatically. Thatâs important in that it means tooling can test for passing values. It also means algorithms can, for example, return an appropriate text color when passed in the background color. So our tool will incorporate contrast checking as an additional way to gauge how to adjust your palette.
The functions demonstrated in this project can be extracted for helping plan a contrast-safe design system palette, or baked into a Sass framework that allows defining a custom theme.
Sass as a palette building toolSass provides several traditional programming features that make it perfect for our needs, such as creating and iterating through arrays and manipulating values with custom functions. When coupled with an online IDE, like CodePen, that has real-time processing, we can essentially create a web app to solve specific problems such as building a color palette.
Here is a preview of the tool weâre going to be using:
See the Pen
Sass Color Palette Grapher by Stephanie Eckles (@5t3ph)
on CodePen.
- It outputs an aspect ratio-controlled responsive graph for accurate plot point placement and value comparing.
- It leverages the result of Sass color functions and math calculations to correctly plot points on a 0â100% scale.
- It generates a gradient to provide a more traditional "swatch" view.
- It uses built-in Sass functions to extract saturation and lightness values.
- It creates luminance and contrast functions (forked from Material Web Components in addition to linking in required precomputed linear color channel values).
- It returns appropriate text color for a given background, with a settings variable to change the ratio used.
- It provides functions to uniformly scale saturation and lightness across a given palette.
To begin, you may wish to swap from among the provided example palettes to get a feel for how the graph values change for different types of color ranges. Simply copy a palette variable name and swap it for $default as the value of the $palette variable which can be found under the comment SWAP THE PALETTE VARIABLE.
Next, try switching the $contrastThreshold variable value between the predefined ratios, especially if you are less familiar with ensuring contrast passes WCAG guidelines.
Then try to adjust the $palette-scale-lightness or $palette-scale-saturation values. Those feed into the palette function and uniformly scale those measurements across the palette (up to the individual color's limit).
Finally, have a go at adding your own palette, or swap out some colors within the examples. The tool is a great way to explore Sass color functions to adjust particular attributes of a color, some of which are demonstrated in the $default palette.
Interpreting the graphs and creating balanced, accessible palettesThe graphing tool defaults to displaying luminance due to it being the most reliable indicator of a balanced palette, as we discussed earlier. Depending on your needs, saturation and lightness can be useful metrics on their own, but mostly they are signalers that can help point to what needs adjusting to bring a palette's luminance more in alignment. An exception may be creating a lightness scale based on each value in your established palette. You can swap to the $stripeBlue example for that.
The $default palette is actually in need of adjustment to get closer to balanced luminance:
The $default paletteâs luminance graphA palette that shows well-balanced luminance is the sample from Stripe ($stripe):
The $stripe palette luminance graphHere's where the tool invites a mind shift. Instead of manipulating a color wheel, it leverages Sass functions to programmatically adjust color attributes.
Check the saturation graph to see if you have room to play with the intensity of the color. My recommended adjustment is to wrap your color value with the scale-color function and pass an adjusted $saturation value, e.g. example: scale-color(#41b880, $saturation: 60%). The advantage of scale-color is that it fluidly adjusts the value based on the given percent.
Lightness can help explain why two colors feel different by assigning a value to their brightness measured against mixing them with white or black. In the $default palette, the change-color function is used for purple to align it's relative $lightness value with the computed lightness() of the value used for the red.
The scale-color function also allows bundling both an adjusted $saturation and $lightness value, which is often the most useful. Note that provided percents can be negative.
By making use of Sass functions and checking the saturation and lightness graphs, the $defaultBalancedLuminance achieves balanced luminance. This palette also uses the map-get function to copy values from the $default palette and apply further adjustments instead of overwriting them, which is handy for testing multiple variations such as perhaps a hue shift across a palette.
The $defaultBalancedLuminance luminance graphTake a minute to explore other available color functions.
http://jackiebalzer.com/color offers an excellent web app to review effects of Sass and Compass color functions.Contrast comes into play when considering how the palette colors will actually be used in a UI. The tool defaults to the AA contrast most appropriate for all text: 4.5. If you are building for a light UI, then consider that any color used on text should achieve appropriate contrast with white when adjusting against luminance, indicated by the center color of the plot point.
Tip: The graph is set up with a transparent background, so you can add a background rule on body if you are developing for a darker UI.
Further readingColor is an expansive topic and this article only hits the aspects related to Sass functions. But to truly understand how to create harmonious color systems, I recommend the following resources:
- Color Spaces - is a super impressive deep-dive with interactive models of various color spaces and how they are computed.
- Understanding Colors and Luminance - A beginner-friendly overview from MDN on color and luminance and their relationship to accessibility.
- Perpetually Uniform Color Spaces - More information on perceptually uniform color systems, with an intro the tool HSLuv that converts values from the more familiar HSL color space to the luminance-tuned CIELUV color space.
- Accessible Color Systems - A case study from Stripe about their experience building an accessible color system by creating custom tooling (which inspired this exploration and article).
- A Nerd's Guide to Color on the Web - This is a fantastic exploration of the mechanics of color on the web, available right here on CSS-Tricks.
- Tanaguru Contrast Finder - An incredible tool to help if you are struggling to adjust colors to achieve accessible contrast.
- ColorBox - A web app from Lyft that further explores color scales through graphing.
- Designing Systematic Colors - Describes Mineral UI's exceptional effort to create color ramps to support consistent theming via a luminance-honed palette.
- How we designed the new color palettes in Tableau 10 - Tableau exposed features of their custom tool that helped them create a refreshed palette based on CIELAB, including an approachable overview of that color space.
The post A Handy Sass-Powered Tool for Making Balanced Color Palettes appeared first on CSS-Tricks.
Motion Paths â Past, Present and Future
Cassie Evans has a great intro to motion paths. That is, being able to animate an element along a path. Not just up/down/left/right, but whatever curvy/wiggly/weird path you want.
It's an interesting subject because there are so many different technologies helping to do it over time. SMIL, JavaScript-powered animation libraries, native JavaScript APIs, and even CSS via offset-path and friends. I think offset-path is funny - it was changed to that name from motion-path as you don't technically have to apply motion to an element you place on a path in this way.
There's no clear winner. I'm (perhaps obviously) a fan of doing stuff like this in CSS whenever possible, but the browser support there is essentially Chrome-only. Plus seeing SVG path values in CSS always feels a smidge uncomfortable because of the unitless numbers. SMIL feels like essentially dead technology, but at least then you're in SVG-land and the paths make good sense in that context. If browser support is vital, you have to use a library.
I do think there is untapped cool design possibility in motion paths. It's not just for landing space ships, but can be for practical stuff like how a modal enters a page.
Direct Link to Article — Permalink
The post Motion Paths â Past, Present and Future appeared first on CSS-Tricks.
Case Study: lynnandtonic.com 2019 refresh
Lynn Fisher walks us step-by-step through the redesign process of her latest outstanding personal website. In this design, increasing the width of the browser window will cause the illustrations on the page crack to open and reveal more within them:
This case study reminded me that Lynn also has an archive of every case study and project that sheâs made over the years and that it's most certainly worth checking out.
Direct Link to Article — Permalink
The post Case Study: lynnandtonic.com 2019 refresh appeared first on CSS-Tricks.
Techniques for Rendering Text with WebGL
As is the rule in WebGL, anything that seems like it should be simple is actually quite complicated. Drawing lines, debugging shaders, text rendering⊠they are all damn hard to do well in WebGL.
Isnât that weird? WebGL doesn't have a built-in function for rendering text. Although text seems like the most basic of functionalities. When it comes down to actually rendering it, things get complicated. How do you account for the immense amount of glyphs for every language? How do you work with fixed-width, or proportional-width fonts? What do you do when text needs to be rendered top-to-bottom, left-to-right, or right-to-left? Mathematical equations, diagrams, sheet music?
Suddenly it starts to make sense why text rendering has no place in a low-level graphics API like WebGL. Text rendering is a complex problem with a lot of nuances. If we want to render text, we need to get creative. Fortunately, a lot of smart folks already came up with a wide range of techniques for all our WebGL text needs.
We'll learn at some of those techniques in this article, including how to generate the assets they need and how to use them with ThreeJS, a JavaScript 3D library that includes a WebGL renderer. As a bonus, each technique is going to have a demo showcasing use cases.
Table of Contents- A quick note on text outside of WebGL
- Font geometries
- Text (and canvas) textures
- Bitmap fonts
- Signed distance fields
- Multi-channel signed distance fields
- Generating 3D text with height maps
- Further reading
Although this article is all about text inside WebGL, the first thing you should consider is whether you can get away with using HMTL text or canvas overlayed on top of your WebGL canvas. The text can't be occluded with the 3D geometry as an overlay, but you can get styling and accessibility out of the box. That's all you need in a lot of cases.
Font geometriesOne of the common ways to render text is to build the glyphs with a series of triangles, much like a regular model. After all, rendering points, lines and triangles are a strength of WebGL.
When creating a string, each glyph is made by reading the triangles from a font file of triangulated points. From there, you could extrude the glyph to make it 3D, or scale the glyphs via matrix operations.
Regular font representation (left) and font geometry (right)Font geometry works best for a small amount of text. Thatâs because each glyph contains many triangles, which causes drawing to become problematic.
Rendering this exact paragraph you are reading right now with font geometry creates 185,084 triangles and 555,252 vertices. This is just 259 letters. Write the whole article using a font geometry and your computer fan might as well become an airplane turbine.
Although the number of triangles varies by the precision of the triangulation and the typeface in use, rendering lots of text will probably always be a bottleneck when working with font geometry.
How to create a font geometry from a font fileIf it were as easy as choosing the font you want and rendering the text. I wouldn't be writing this article. Regular font formats define glyphs with Bezier curves. On the flip side, drawing those in WebGL is extremely expensive on the CPU and is also complicated to do. If we want to render text, we need to create triangles (triangulation) out of Bezier curves.
I've found a few triangulation methods, but by no means are any of them perfect and they may not work with every font. But at least they'll get you started for triangulating your own typefaces.
Method 1: Automatic and easyIf you are using ThreeJS, you pass your typeface through FaceType.js to read the parametric curves from your font file and put them into a .json file. The font geometry features in ThreeJS take care of triangulating the points for you:
const geometry = new THREE.FontGeometry("Hello There", {font: font, size: 80})Alternatively, if you are not using ThreeJS and don't need to have real-time triangulation. You could save yourself the pain of a manual process by using ThreeJS to triangulate the font for you. Then you can extract the vertices and indices from the geometry, and load them in your WebGL application of choice.
Method 2: Manual and painfulThe manual option for triangulating a font file is extremely complicated and convoluted, at least initially. It would require a whole article just to explain it in detail. That said, we'll quickly go over the steps of a basic implementation I grabbed from StackOverflow.
See the Pen
Triangulating Fonts by Daniel Velasquez (@Anemolo)
on CodePen.
The implementation basically breaks down like this:
- Add OpenType.js and Earcut.js to your project.
- Get Bezier curves from your .tff font file using OpenType.js.
- Convert Bezier curves into closed shapes and sort them by descending area.
- Determine the indices for the holes by figuring out which shapes are inside other shapes.
- Send all of the points to Earcut with the hole indices as a second parameter.
- Use Earcut's result as the indices for your geometry.
- Breath out.
Yeah, it's a lot. And this implementation may not work for all typefaces. It'll get you started nonetheless.
Using text geometries in ThreeJSThankfully, ThreeJS supports text geometries out of the box. Give it a .json of your favorite font's Bezier curves and ThreeJS takes care of triangulating the vertices for you in runtime.
var loader = new THREE.FontLoader(); var font; var text = "Hello World" var loader = new THREE.FontLoader(); loader.load('fonts/helvetiker_regular.typeface.json', function (helvetiker) { font = helvetiker; var geometry = new THREE.TextGeometry(text, { font: font, size: 80, height: 5, }); } Advantages- Itâs easily extruded to create 3D strings.
- Scaling is made easier with matrix operations.
- It provides great quality depending on the amount of triangles used.
- This doesn't scale well with large amounts of text due to the high triangle count. Since each character is defined by a lot of triangles, even rendering something as brief as "Hello World" results in 7,396 triangles and 22,188 vertices.
- This doesn't lend itself to common text effects.
- Anti-aliasing depends on your post-processing aliasing or your browser default.
- Scaling things too big might show the triangles.
In the following demo, I took advantage of how easy it is to create 3D text using font geometries. Inside a vertex shader, the extrusion is increased and decreased as time goes on. Pair that with fog and standard material and you get these ghostly letters coming in and out of the void.
Notice how with a low amount of letters the amount of triangles is already in the tens of thousands!
Text (and canvas) texturesMaking text textures is probably the simplest and oldest way to draw text in WebGL. Open up Photoshop or some other raster graphics editor, draw an image with some text on it, then render these textures onto a quad and you are done!
Alternatively, you could use the canvas to create the textures on demand at runtime. Youâre able to render the canvas as a texture onto a quad as well.
Aside for being the least complicated technique of the bunch. Text textures and canvas textures have the benefit of only needed one quad per texture, or given piece of text. If you really wanted to, you could write the entire British Encyclopedia on a single texture. That way, you only have to render a single quad, six vertices and two faces. Of course, you would do it in a scale, but the idea still remains: You can batch multiple glyphs into same quad. Both text and canvas textures experience have issues with scaling, particularly when working with lots of text.
For text textures, the user has to download all the textures that make up the text, then keep them in memory. For canvas textures, the user doesn't have to download anything â but now the user's computer has to do all the rasterizing at runtime, and you need to keep track of where every word is located in the canvas. Plus, updating a big canvas can be really slow.
How to create and use a text textureText textures don't have anything fancy going on for them. Open up your favorite raster graphics editor, draw some text on the canvas and export it as an image. Then you can load it as a texture, and map it on a plane:
// Load texture let texture = ; const geometry = new THREE.PlaneBufferGeometry(); const material new THREE.MeshBasicMaterial({map: texture}); this.scene.add(new Mesh(geometry,material));If your WebGL app has a lot of text, downloading a huge sprite sheet of text might not be ideal, especially for users on slow connections. To avoid the download time, you can rasterize things on demand using an offscreen canvas then sample that canvas as a texture.
Letâs trade download time for performance since rasterizing lots of text takes more than a moment.
function createTextCanvas(string, parameters = {}){ const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // Prepare the font to be able to measure let fontSize = parameters.fontSize || 56; ctx.font = `${fontSize}px monospace`; const textMetrics = ctx.measureText(text); let width = textMetrics.width; let height = fontSize; // Resize canvas to match text size canvas.width = width; canvas.height = height; canvas.style.width = width + "px"; canvas.style.height = height + "px"; // Re-apply font since canvas is resized. ctx.font = `${fontSize}px monospace`; ctx.textAlign = parameters.align || "center" ; ctx.textBaseline = parameters.baseline || "middle"; // Make the canvas transparent for simplicity ctx.fillStyle = "transparent"; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillStyle = parameters.color || "white"; ctx.fillText(text, width / 2, height / 2); return canvas; } let texture = new THREE.Texture(createTextCanvas("This is text"));Now you can use the texture on a plane, like the previous snippet. Or you could create a sprite instead.
As an alternative, you could use more efficient libraries to create texture or sprites, like three-text2d or three-spritetext. And if you want text multi-line text, you should check out this amazing tutorial.
Advantages- This provides great 1-to-1 quality with static text.
- Thereâs a low vertex/face count. Each string can use as little as six vertices and two faces.
- Itâs easy to implement texture on a quad.
- Itâs fairly trivial to add effects, like borders and glows, using canvas or a graphics editor.
- Canvas makes it easy to create multi-line text.
- Looks blurry if scaled, rotated or transformed after rasterizing.
- On-non retina, text looks crunchy.
- You have to rasterize all the strings used. A lot of strings means a lot of data to download.
- On-demand rasterizing with canvas can be slow if you keep constantly updating the canvas.
Canvas textures work well with a limited amount of text that doesnât change often. So I built a simple wall of text with the quads re-using the same texture.
Bitmap fontsBoth font geometries and text textures experience the same problems handling lots of text. Having one million vertices per piece of text is super inefficient, and creating one texture per piece of text doesn't really scale.
Bitmap fonts solve this issue by rasterizing all unique glyphs into a single texture, called a texture atlas. This means you can assemble any given string at runtime by creating a quad for each glyph and sampling the section of the texture atlas.
This means users only have to download and use a single texture for all of the text. It also means you only need to render as little as one quad per glyph:
A visual of bitmap font samplingRendering this whole article would be approximately 117,272 vertices and 58,636 triangles. That's 3.1 times more efficient compared to a font geometry rendering just a single paragraph. That a huge improvement!
Because bitmap fonts rasterize the glyph into a texture, they suffer from the same problem as regular images. Zoom in or scale and you start seeing a pixelated and blurry mess. If you want text at a different size, you should send a secondary bitmap with the glyphs on that specific size. Or you could use a Signed Distance Field (SDF) which weâll cover in the next section.
How to create bitmap fontsThere are a lot of tools to generate bitmaps. Here are some of the more relevant options out there:
- Angelcode's bmfont - This is by the creators of the bitmap format.
- Hiero - This is a Java open-source tool. Itâs very similar to Anglecode's bmfont, but it allows you to add text effects.
- Glyphs Designer - This is a paid MacOS app.
- ShoeBox - This is an tool for dealing with sprites, including bitmap fonts.
We'll use Anglecode's bmfont for our example because I think it's the easiest one to get started. At the bottom of this section, you can find other tools if you think it lacks the functionality you are looking for.
When you open the app, you'll see a screen full of letters that you can select to use.The nice thing about this is that you are able to grab just the glyphs you need instead of sending Greek symbols.
The appâs sidebar allows you to choose and select groups of glyphs.
The BmFont applicationReady to export? Go to Options → Save bitmap as. And done!
But weâre getting a little ahead of ourselves. Before exporting, there are a few important settings you should check.
Export and Font Option settings- Font settings: This let you choose the font and size you want to use. The most important item here is "Match char height." By default, the appâs "size" option uses pixels instead of points. You'll see a substantial difference between your graphics editor's font size and the font size that is generated. Select the "Match char height" options if you want your glyphs to make sense.
- Export settings: For the export, make sure the texture size is a power of two (e.g. 16x16, 32x32, 64x64, etc.). Then you are able to take advantage of "Linear Mipmap linear" filtering, if needed.
At the bottom of the settings, you'll see the "file format" section. Choosing either option here is fine as long as you can read the file and create the glyphs.
If you are looking for the smallest file size. I ran a ultra non-scientific test where I created a bitmap of all lowecase and uppercase Latin characters and compared each option. For Font Descriptors, the most efficient format is Binary.
Font Descriptor Format File Size Binary 3 KB Raw Text 11 KB XML 12 KB Texture Format File Size PNG 7 KB Targa 64 KB DirectDraw Surface 65 KBPNG is the smallest file size for Text Texture.
Of course, it's a little more complicated than just file sizes. To get a better idea of which option to use, you should look into parsing time and run-time performance. If you would like to know the pros and cons of each formats, check out this discussion.
How to use bitmap fontsCreating bitmap font geometry is a bit more involved than just using a texture because we have to construct the string ourselves. Each glyph has its own height and width, and samples a different section of the texture. We have to create a quad for each glyph on our string so we can give them the correct UVs to sample it's glyph.
You can use three-bmfont-text in ThreeJS to create strings using bitmaps, SDFs and MSDFs. It takes care of multi-line text, and batching all glyphs onto a single geometry. Note that it needs to be installed in a project from npm.
var createGeometry = require('three-bmfont-text') var loadFont = require('load-bmfont') loadFont('fonts/Arial.fnt', function(err, font) { // create a geometry of packed bitmap glyphs, // word wrapped to 300px and right-aligned var geometry = createGeometry({ font: font, text: "My Text" }) var textureLoader = new THREE.TextureLoader(); textureLoader.load('fonts/Arial.png', function (texture) { // we can use a simple ThreeJS material var material = new THREE.MeshBasicMaterial({ map: texture, transparent: true, color: 0xaaffff }) // now do something with our mesh! var mesh = new THREE.Mesh(geometry, material) }) })Depending whenever your text is drawn as as full black or full white, use the invert option.
Advantages- Itâs fast and simple to render.
- Itâs a 1:1 ratio and resolution independent.
- It can render any string, given the glyphs.
- Itâs good for lots of text that needs to change often.
- Itâs works extremely well with a limited number of glyphs.
- Itâs includes support for things like kerning, line height and word-wrapping at run-time.
- It only accepts a limited set of characters and styles.
- It requires pre-rasterizing glyphs and extra bin packing for optimal usage.
- Itâs blurry and pixelated at large scales, and can also be rotated or transformed.
- Thereâs only one quad per rendered glyph.
Raster bitmap fonts work great for movie credits because we only need a few sizes and styles. The drawback is that the text isnât great with responsive designs because itâll look blurry and pixelated at larger sizes.
For the mouse effect, Iâm making calculations by mapping the mouse position to the size of the view then calculating the distance from the mouse to the text position. Iâm also rotating the text when it hits specific points on the z-axis and y-axis.
Signed distance fieldsMuch like bitmap fonts, signed distance field (SDF) fonts are also a texture atlas. Unique glyphs are batch into a single "texture atlas" that can create any string at runtime.
But instead of storing the rasterized glyph on the texture the way bitmap fonts do, the glyph's SDF is generated and stored instead which allows for a high resolution shape from a low resolution image.
Like polygonal meshes (font geometries), SDFs represent shapes. Each pixel on an SDF stores the distance to the closest surface. The sign indicates whenever the pixel is inside or outside the shape. If the sign is negative, then the pixel is inside; if itâs positive, then the pixel is outside. This video illustrates the concept nicely.
SDFs are also commonly used for raytracing and volumetric rendering.
Because an SDF stores distance in each pixel, the raw result looks like a blurry version of the original shape. To output the hard shape youâll need to alpha test it at 0.5 which is the border of the glyph. Take a look at how the SDF of the letter "A" compares to it's regular raster image:
Raster text beside of a raw and an alpha tested SDFAs I mentioned earlier, the big benefit of SDFs is being able to render high resolution shapes from low resolution SDF. This means you can create a 16pt font SDF and scale the text up to 100pt or more without losing much crispness.
SDFs are good at scaling because you can almost perfectly reconstruct the distance with bilinear interpolation, which is a fancy way of saying we can get values between two points. In this case, bilinear interpolating between two pixels on a regular bitmap font gives us the in-between color, resulting in a linear blur.
On an SDF, bilinear interpolating between two pixels provides the in-between distance to the nearest edge. Since these two pixel distances are similar to begin with, the resulting value doesn't lose much information about the glyph. This also means the bigger the SDF, the more accurate and less information is lost.
However, this process comes with a caveat. If the rate change between pixels is not linear â like in the case of sharp corners â bilinear interpolation gives out an inaccurate value, resulting in chipped or rounded corners when scaling an SDF much higher than its original size.
SDF rounded cornersAside from bumping the texture side, the only real solution is to use multi-channel SDFs, which is what weâll cover in the next section.
If you want to take a deeper dive into the science behind SDFs, check out the Chris Greenâs Masterâs thesis (PDF) on the topic.
Advantages- They maintain crispness, even when rotated, scaled or transformed.
- They are ideal for dynamic text.
- They provide good quality to the size ratio. A single texture can be used to render tiny and huge font sizes without losing much quality.
- They have a low vertex count of only four vertices per glyph.
- Antialiasing is inexpensive as is making borders, drop shadows and all kinds of effects with alpha testing.
- Theyâre smaller than MSDFs (which weâll see in a bit).
- The can result in rounded or chipped corners when the texture is sampled beyond its resolution. (Again, weâll see how MSDFs can prevent that.)
- Theyâre ineffective at tiny font sizes.
- They can only be used with monochrome glyphs.
Multi-channel signed distance field (MSDF) fonts is a bit of a mouthful and a fairly recent variation on SDFs that is capable of producing near-perfect sharp corners by using all three color channels. They do look quite mind blowing at first but don't let that put you off because they are easy to use than they appear.
A multi-channel signed distance field file can look a little spooky at first.Using all three color channels does result in a heavier image, but thatâs what gives MSDFs a far better quality-to-space ratio than regular SDFs. The following image shows the difference between an SDF and an MSDF for a font that has been scaled up to 50px.
The SDF font results in rounded corners, even at 1x zoom, while the MSDF font retains sharp edges, even at 5x zoom.Like a regular SDF, an MSDF stores the distance to the nearest edge but changes the color channels whenever it finds a sharp corner. We get the shape by drawing where two color channels or more agree. Although there's a bit more technique involved. Check out the README for this MSDF generator for a more thorough explanation.
Advantages- They support a higher quality and space ratio than SDFs. and are often the better choice.
- They maintain sharp corners when scaled.
- They may contain small artifacts but those can be avoided by bumping up the texture size of the glyph.
- They requires filtering the median of the three values at runtime which is a bit expensive.
- They are only compatible with monochrome glyphs.
The quickest way to create an MSDF font is to use the msdf-bmfont-web tool. It has most of the relevant customization options and it gets the job done in seconds, all in the browser. Alternatively, there are a number of Google Fonts that have already been converted into MSDF by the folks at A-Frame.
If you are also looking to generate SDFs or your typeface, it requires some special tweaking thanks to some problematic glyphs. The msdf-bmfont-xml CLI gives you a wide range of options, without making things overly confusing. Letâs take a look at how you would use it.
First, you'll need to install globally it from npm:
npm install msdf-bmfont-xml -gNext, give it a .ttf font file with your options:
msdf-bmfont ./Open-Sans-Black.ttf --output-type json --font-size 76 --texture-size 512,512Those options are worth digging into a bit. While msdf-bmfont-xml provides a lot of options to fine-tune your font, there are really just a few options you'll probably need to correctly generate an MSDF:
- -t <type> or <code>--field-type <msdf or sdf>: msdf-bmfont-xml generates MSDFs glyph atlases by default. If you want to generate an SDF instead, you need to specify it by using -t sdf.
- -f <xml or json> or --output-type <xml or json>: msdf-bmfont-xml generates an XML font file that you would have to parse to JSON at runtime. You can avoid this parsing step by exporting JSON straight away.
- -s, --font-size <fontSize>: Some artifacts and imperfections might show up if the font size is super small. Bumping up the font size will get rid of them most of the time. This example shows a small imperfection in the letter "M."
- -m <w,h> or --texture-size <w,h>: If all your characters don't fit in the same texture, a second texture is created to fit them in. Unless you are trying to take advantage of a multi-page glyph atlas, I recommend increasing the texture size so that it fits over all of the characters on one texture to avoid extra work.
There are other tools that help generate MSDF and SDF fonts:
- msdf-bmfont-web: A web tool to create MSDFs (but not SDFs) quickly and easily
- msdf-bmfont: A Node tool using Cairo and node-canvas
- msdfgen: The original command line tool that all other MSDF tools are based from
- Hiero: A tool for generating both bitmaps and SDF fonts
Because SDF and MSDF fonts are also glyph atlases, we can use three-bmfont-text like we did for bitmap fonts. The only difference is that we have to get the glyph our of the distance fields with a fragment shader.
Hereâs how that works for SDF fonts. Since our distance field has a value greater than .5 outside our glyph and less than 0.5 inside our glyph, we need to alpha test in a fragment shader on each pixel to make sure we only render pixels with a distance less than 0.5, rendering just the inside of the glyphs.
const fragmentShader = ` uniform vec3 color; uniform sampler2D map; varying vec2 vUv; void main(){ vec4 texColor = texture2D(map, vUv); // Only render the inside of the glyph. float alpha = step(0.5, texColor.a); gl_FragColor = vec4(color, alpha); if (gl_FragColor.a < 0.0001) discard; } `; const vertexShader = ` varying vec2 vUv; void main { gl_Position = projectionMatrix * modelViewMatrix * position; vUv = uv; } `; let material = new THREE.ShaderMaterial({ fragmentShader, vertexShader, uniforms: { map: new THREE.Uniform(glyphs), color: new THREE.Uniform(new THREE.Color(0xff0000)) } })Similarly, we can import the font from three-bmfont-text which comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:
let SDFShader = require('three-bmfont-text/shaders/sdf'); let material = new THREE.RawShaderMaterial(MSDFShader({ map: texture, transparent: true, color: 0x000000 }));MSDF fonts are a little different. They recreate sharp corners by the intersections of two color channels. Two or more color channels have to agree on it. Before doing any alpha texting, we need to get the median of the three color channels to see where they agree:
const fragmentShader = ` uniform vec3 color; uniform sampler2D map; varying vec2 vUv; float median(float r, float g, float b) { return max(min(r, g), min(max(r, g), b)); } void main(){ vec4 texColor = texture2D(map, vUv); // Only render the inside of the glyph. float sigDist = median(texColor.r, texColor.g, texColor.b) - 0.5; float alpha = step(0.5, sigDist); gl_FragColor = vec4(color, alpha); if (gl_FragColor.a < 0.0001) discard; } `; const vertexShader = ` varying vec2 vUv; void main { gl_Position = projectionMatrix * modelViewMatrix * position; vUv = uv; } `; let material = new THREE.ShaderMaterial({ fragmentShader, vertexShader, uniforms: { map: new THREE.Uniform(glyphs), color: new THREE.Uniform(new THREE.Color(0xff0000)) } })Again, we can also import from three-bmfont-text using its MSDFShader which also comes with antialiasing out of the box. Then we can use it directly on a RawShaderMaterial:
let MSDFShader = require('three-bmfont-text/shaders/msdf'); let material = new THREE.RawShaderMaterial(MSDFShader({ map: texture, transparent: true, color: 0x000000 })); Demo: Star Wars introThe Star Wars drawl intro is a good example where MSDF and SDF fonts work well because the effect needs the text to come in multiple sizes. We can use a single MSDF and the text always looks sharp! Although, sadly, three-bm-font doesnât support justified text yet. Applying left justification would make for a more balanced presentation.
For the light saber effect, Iâm raycasting an invisible plane the size of the plane, drawing onto a canvas thatâs the same size, and sampling that canvas by mapping the scene position to the texture coordinates.
Bonus tip: Generating 3D text with height mapsAside from font geometries, all the techniques weâve covered generate strings or glyphs on a single quad. If you want to build 3D geometries out of a flat texture, your best choice is to use a height map.
A height map is a technique where the geometry height is bumped up using a texture. This is normally used to generate mountains in games, but it turns out to be useful rendering text as well.
The only caveat is that youâll need a lot of faces for the text to look smooth.
Further readingDifferent situations call for different techniques. Nothing we saw here is a silver bullet and they all have their advantages and disadvantages.
There are a lot of tools and libraries out there to help make the most of WebGL text, most of which actually originate from outside WebGL. If you want to keep learning, I highly recommend you go beyond WebGL and check out some of these links:
- How many ways to load a font in ThreeJS
- Case Study on a Text heavy Spotify campaign
- Interesting conversations about MSDFs
- More Font Rendering Techniques
- Rendering vector art (glyphs) on the GPU
- How FreeType does text rendering
The post Techniques for Rendering Text with WebGL appeared first on CSS-Tricks.
CSS Architecture for Modern JavaScript Applications
There is a lot to like from Mike Riethmuller here:
- The title. When you're building a website from JavaScript-powered components anyway, that is a moment to talk about how to do styling, because it opens some doors to JavaScript-powered styles that you probably wouldn't otherwise choose.
- The personal experience and pragmatism. Drawing on five years of consulting, he's seeing that component re-use and style understandability is suffering, not improving, partly due to every team having different approaches. He says "it's a little bit of everybody's fault" and sees the perspective of others who like parts of what JavaScript-powered styles can bring, like less dependence on specificity.
- The fresh thinking. Since JavaScript-powered websites are all built by nested components anyway, why not use that architecture for styling? The thesis of the article is really about building UI components that, on purpose, don't involve application logic but exist just for styling, and using a combination of clever CSS and JavaScript power to the kind of styling you need.
Direct Link to Article — Permalink
The post CSS Architecture for Modern JavaScript Applications appeared first on CSS-Tricks.
Weekly Platform News: Upgrading Navigations to HTTPS, Sale of .org Domains, New Browser Engine
In this week's roundup: DuckDuckGo gets smarter encryption, a fight over the sale of dot org domains, and a new browser engine is in the works.
Let's get into the news!
DuckDuckGo upgrades and open-sources its encryptionDuckDuckGo has open-sourced its âSmarter Encryptionâ technology that enables upgrading from HTTP to HTTPS, and Pinterest (a popular social network) is already using it for outbound traffic — when people navigate from Pinterest to other websites — with great results: Their outbound HTTPS traffic increased from 60% to 80%.
DuckDuckGo uses its crawler to automatically generate and maintain a huge list of websites that support HTTPS, approximately 12 million entries. For comparison, Chromiumâs HSTS Preload List contains only about 85 thousand entries.
(via DuckDuckGo)
Nonprofits oppose the sale of the .org domainA coalition of organizations consisting of EFF, Wikimedia, and many others, are urging the Internet Society to stop the sale of the nonprofit organization that operates the .org domain to an investment firm.
Non-governmental organizations (NGOs) all over the world rely on the .org top-level domain. [...] We cannot afford to put them into the hands of a private equity firm that has not earned the trust of the NGO community.
In a separate blog post, Mark Surman (CEO of Mozilla Foundation) urges the Internet Society to âstep back and provide public answers to questions of interest to the public and the millions of organizations that have made dot org their home online for the last 15 years.â
(via Elliot Harmon)
A new browser engine is in developmentEkioh (a company from Cambridge, U.K.) is developing an entirely new browser engine for their Flow browser, which also includes Mozillaâs SpiderMonkey as its JavaScript engine. The browser can already run web apps such as Gmail (mostly), and the company plans to release it on desktop soon.
(via Flow Browser)
More news...Read more news in my weekly newsletter for web developers. Pledge as little as $2 per month to get the latest news from me via email every Monday.
Auto Layout lands in Figma
Hereâs a fresh update to my favorite design tool that is thoroughly exciting: Auto layout! That means we can make frames that resize based on the size of the content within it. That's particularly useful for buttons in a design system where you want to drop a button on the page and then keep its left and right padding equal.
However! The other exciting part of this update is that content can now be nudged down based on the content above it, too â just like on a webpage. If youâre interested in trying out this feature then Iâd recommend just booting up Figma where you can immediately hop over to a playground that explains how this game-changing new feature works with a handy step-by-step tutorial.
The Rising Complexity of JAMstack Sites and How to Manage Them
When you add anything with user-generated content or dynamic data to a static site, the complexity of the build process can become comparable to launching a monolithic CMS. How can we add rich content to static sites without stitching together multiple third-party services?
For people in the development community static site generators are a popular choice over traditional content management systems (CMS) like WordPress. By comparison static sites are usually lightweight, highly configurable, fast, easy to use and can be deployed almost anywhere.
With static websites, no code is generated on the server; we've replaced databases and server-side code with APIs and build processes.
This has become known as a JAMstack, which stands for JavaScript, APIs and Markup. I have a strong persuasion towards JAMstack sites because I feel more in control of the output than I do when working with the often large and monolithic CMSs Iâve sometimes had to use on client projects.
Despite my enthusiasm, I'm often disheartened by the steep complexity curve I typically encounter about halfway through a JAMstack project. Normally the first few weeks are incredibly liberating. It's easy to get started, there is good visible progress, everything feels lean and fast. Over time, as more features are added, the build steps become more complex, multiple APIs are added, and suddenly everything feels slow. In other words, the development experience begins to suffer.
It usually looks something like this:
One of the reasons for this steep rise in complexity is there are limits to the type of data that markdown can easily represent. Relationships are one example where static sites struggle. Relationships between pages or collections of assets (such as an image gallery) can only be represented by markdown in inefficient ways. It requires significant preprocessing to resolve anything more complicated than a simple set or tags or categories. If youâve ever had to do it, you will also know the authoring experience of managing relationships in markdown isnât ideal.
User-generated content is another area that can cause a steep rise in the complexity of static sites. Adding features like comments, ratings, likes or any other kind of dynamic content will require third-party services â each has its own account that needs to be managed, not to mention that adding third-party scripts can have a negative impact of page performance.
If a service doesn't match your specific requirements, sometimes itâs possible to cobble solutions together using generic platforms like Google Forms or AirTable.
The end result is we've outsourced the database, fragmented the content management experience and stitched together a bundle of compromises. Thatâs a stark contrast from the initial ease of setting up and deploying a JAMstack site.
Although this complexity curve is not unique to JAMstack projects, adding rich features to markdown-driven sites is far more difficult than we care to admit. What happened!? A lack of complexity was one of the reasons we favored JAMstack in the first place.
We did that thing that web developers do. We moved the complexity from one space into another and congratulated ourselves 😂. Not having complexity on the server-side is good for front-end performance, but there is little incentive to optimize any further once we do this. Ridiculous build times and complicated tool-chains have become an acceptable reality for modern front-end web development.
JAMstack PlusBefore I come across as sounding too critical, I should make it clear that I absolutely love static site generators. I think they are a perfect solution for many simple sites and you should still use them. However, I feel like a simple content management layer that I own and can configure is preferable to:
- poor content management experiences,
- complicated integration of third-party services, and
- inefficient build processes.
I want to combine the benefits of a CMS with static site generators.
And it seems I'm not the only one who has reached this conclusion:
WordPress (et al) is often pitted _against_ static site generators.
I get it. They feel like different worlds.
But SSGâs just take input and make static output. You may still want/need a CMS, and WordPress can be that. It has APIs that a SSG can injest.
— Chris Coyier (@chriscoyier) June 11, 2019
My encounters with static site generators lead me to the opinion that they are incredibly compelling as a way to work, but be sure you really do want a static site. Too often what I see is a messy compromise to deal with stuff a DB would make easy.
— Rachel Andrew (@rachelandrew) September 20, 2019
WordPress is a fantastic and familiar content editing experience, but what if you could deploy your site statically for ultimate speed, stability, and security? ⚡️You can, and hereâs a great rundown of plugins for doing headless WordPress on Netlify: https://t.co/lz6fscbX3q
— Netlify (@Netlify) July 30, 2019
The solution doesnât need to be another third-party service or require abandoning static sites entirely. You can use a personalized content management layer and unified API to enrich a static site. It might not be as hard as you think.
The first step is to create an API for your site. You can use any headless CMS, but the challenge Iâve had with many options is they make a lot of assumptions about the type of content you want. You might not want the CMS to manage pages and posts, but rather use it to store comments or images. I find this particularly difficult with WordPress. I often feel like Iâm forcing a blogging platform to be just the service I need.
The new version of KeystoneJS (Keystone 5) is be an excellent alternative to more opinionated content management systems. It's made up of tiny independent components so you only add the parts you need. This means it doesnât feel like modifying a blogging platform. Instead it's like creating a personalized mini-CMS and API to work specifically with your site.
I call this approach JAMstack Plus.
To help you get started with this idea I've created two projects:
- Supermaya, a starter kit for the static site generator Eleventy.
- Keystone JAMstack Plus, a blog enrichment platform.
The first project I want to share with you is Supermaya, an Eleventy starter kit designed to help add rich features to a blog or website without a complicated build process.
It comes with the all "blog standard" features including:
- Posts and Pages
- Pagination
- Tags
- RSS feed
- Service worker
- Lazy loading images
- Critical CSS (if enabled)
It also has considerate and accessible markup. If deployed correctly, it should get full scores on a lighthouse audit out-of-the-box:
Supermaya scores 100% on Lighthouse tests.I didnât build Supermaya specifically as a platform to add user-generated content to static sites. Instead, I started it because I was not satisfied with the way existing static site generators integrate with other build tools. Thatâs why all the pre-processing steps in Supermaya are built into Eleveny itself. This includes the compilation of SCSS and JavaScript. Unifying the compilation steps eliminates the need for build tools like Grunt, Gulp or Webpack running in parallel.
After this, I realized the other reason for increasing complexity on JAMstack sites was integration with third-party services, usually for user-generated content. To solve this, Supermaya has optional tie-ins with a Keystone JAMstack Plus starter-kit, which makes it easier to add user-generated content and other rich features.
You can deploy both Keystone and Supermaya together and connect them at the same time by following the instructions during installation. This will deploy Keystone to Herouku and Supermaya to Netlify, as well as configure your admin user and API URL.
Rich features are added with progressive enhancement, so if the API cannot be reached or there is a server error, the site will continue to function without noticeable degradation or delays for users.
JAMstack Plus starter kitThe Keystone JAMstack Plus starter kit allows you to add rich features to a blog including:
- Comments
- Claps
- Reading list, and
- Logins
Just like Supermaya, it can be used on its own. After itâs deployed, youâll get access to an admin interface that allows you to create and manage content. Youâll also get a GraphQL endpoint that can be connected to Supermaya.
Itâs configured with the intention of being a headless CMS for user-generated content. It expects pages and posts to be managed by a static site generator. However, with a little work â and following the examples in Supermaya â you can connect any front-end to the GraphQL API.
Iâd encourage you to modify the starter-kit: Add additional features or provide content for pages directly from Keystone. If you add features that could be used by the rest of the community contribute back to the starter-kit and we can make it easier for everyone to add rich features to their sites without the need for third-party services.
Note: The automatic deploy will deploy to a free instance of Heroku. This will sleep periodically if not used which can result in slow API response times after periods of inactivity. You can upgrade to a hobby instance to avoid this.
Consider owning your own dataJAMstack and servers are not incompatible. Thereâs always a server (usually multiple) â itâs just a question of who owns it. If you are using any kind of third-party service, the chances are they own your account information, your content and collect user data.
Sometimes this might be an acceptable compromise compared with the overhead of deploying and managing a back-end service, but when the complexity of stitching together several APIs becomes comparable to a CMS, I believe managing a tiny configurable service that you own, can provide a better experience for users, developers and content managers. It also provides a solid platform for websites to grow beyond purely static content into more complicated and varying types of applications.
I donât think JAMstack should defined by pushing all the complexity into the front-end build process or by compromising on developer and user experience. Instead, I think JAMstack should focus on providing lean, configurable static front-ends. These can be connected to APIs to provide user-generated data and content management services. There is no reason not to own and manage these services, if it provides the best outcome.
Firefox 71: First Out of the Gate With Subgrid
A great release from Firefox this week! See the whole roundup post from Chris Mills. I'm personally stoked to see clip-path: path(); go live, which we've been tracking as it's so clearly useful. We also get column-span: all; which is nice in case you're one of the few taking advantages of CSS columns.
But there are two other things I think are a very big deal:
- If you have fluid images (most sites do) via flexible-width containers and img { max-width: 100%; }, you're subject to somewhat janky loading as those images load in because the browser doesn't know how tall the space to reserve needs to be until it knows the dimensions of the image. But now, if you put width/height attributes (e.g. <img width="500" height="250" src="...">), Firefox (and Chrome) will calculate the aspect ratio from those and reserve the correct amount of space. It seems like a small thing, but it really isn't. It will improve the perceived loading for a zillion sites.
- Now we've got subgrid! Remember Eric Meyer called them essential years ago. They allow you to have an element share the grid lines of parent elements instead of needing to establish new ones. The result might be less markup flattening and cleaner designs. A grid of "cards" is a great example here, which Miriam covers in this video showing how you can get much more logical space distribution. It must be in the water, as Anton Ball covers the same concept in this post. I'm a fan of how this is progressive-enhancement friendly. You can still set grid columns/rows on an element for browsers that don't support subgrid, but then display: subgrid; to have them inherit lines instead in supporting browsers.
Filtering Lists Dynamically With Vue on the Server Side is Easier Than Youâd Think
I recently attended the ARTIFACT conference in Austin, TX, and was inspired by a few talks about accessibility through the lens of site performance. It became clear to me that there is this tendency to rely on big JavaScript frameworks to handle the work â like React, Vue, and Angular â but that can be overkill in some cases. That is, negatively affecting site performance, and thus accessibility. At the same time, these frameworks can make development easier and more efficient for developers. My big takeaway from the conference was to see how a fast, performant experience can be balanced with my own development process.
This was on my mind as I was building a list-filtering feature on a project a few days after the conference. Pretty standard stuff: I needed a list of posts and some category filtering. I was using CraftCMS for front-end routes and templating as well as some Vue components here and there for some added JavaScript juiciness. Not a full-on âsingle page appâ but more like a sprinkle of Vue.
The typical way one might approach this is to:
- render the page with an empty div using Craft/Twig
- mount a Vue component to that div
- make an Ajax call from the Vue component to an API to gather the posts as JSON
- render the posts and tie in the filtering.
Since the posts are held as an array within Vue, dynamic list rendering is a pretty cut and dry task.
Simple. Done, right? Well⊠that extra Ajax request means the user is presented with no content on the initial load depending on the userâs network, which might take some time. We could add a loading indicator, but maybe we can do better?
Preferably, the posts are rendered on the initial page request from the CMS.But how do we get the static HTML âhooked upâ with Vue for the filtering?
After taking a step back to rethink the problem, I realized I could use v-if directives to achieve the same thing with some inline JavaScript from Twig (âin the loopâ). Below, Iâll show how I went about it.
My original project used CraftCMS, but Iâm going to do the demos below in WordPress. This is just a concept. It can be applied to CraftCMS/Twig or any other CMS/templating engine combo.
First we need a filtering UI. This will likely go above the start of the loop in an archive template.
<?php $terms = get_terms( [ 'taxonomy' => 'post_tag', // I used tags in this example, but any taxonomy would do 'hide_empty' => true, 'fields' => 'names' ] ); if(!empty($terms)): ?> <div> Filter: <ul class="filters"> <li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''">All</button></li> <?php foreach($terms as $term): ?> <li class="filters__item"> <button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $term; ?>'}" @click="tag = '<?php echo $term; ?>'"><?php echo $term; ?></button> </li> <?php endforeach; ?> </ul> <p aria-live="polite">Showing posts tagged {{ tag ? tag : 'all' }}.</p> </div> <?php endif; ?>Following along with the code, we get some tags from WordPress with get_terms() and output them in a foreach loop. Youâll notice the button for each tag has some Vue directives weâll use later.
We have our loop!
<div class="posts"> <?php // Start the Loop. while ( have_posts() ) : the_post(); <article id="post-<?php the_ID(); ?>" <?php post_class(); ?> v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""' > <header class="entry-header"> <h2><?php the_title(); ?></h2> </header> <div class="entry-content"> <?php the_excerpt(); ?> </div> </article> // End the loop. endwhile; ?> </div>This is a pretty standard WordPress loop for posts. Youâll notice some Vue directives that make use of PHP outputting CMS content.
Aside from some styling, all thatâs left is the Vue âapp.â Are you ready for it? Here it is:
new Vue({ el: '#filterablePosts', data: { 'tag': '' } });Yes, really, thatâs all thatâs needed in the JavaScript file to get this to work!
So, whatâs going on here?Well, instead of some JSON array of posts that gets fed into Vue, we output the posts on the initial page load with WordPress. The trick is to use PHP to output whatâs needed in the Vue directives: v-if and :class.
Whatâs happening on the filter buttons is an onclick event handler (@click) that sets the Vue variable âtagâ to the value of the WordPress post tag.
@click="tag = '<?php echo $term; ?>'"Also, if that Vue variable equals the value of the button (in the :class directive), it adds an active class for the button. This is just for styling.
:class="{'filters__button--active': tag === '<?php echo $term; ?>'}"For the list of articles, we conditionally display them based on the value of the Vue âtagâ variable:
v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""'The PHP function json_encode allows us to output an array of post tags as JavaScript, which means we can use .includes() to see if the Vue âtagâ variable is in that array. We also want to show the article if no tag is selected.
Here it is put together using the Twenty Nineteen theme template archive.php as a base:
<?php get_header(); ?> <section id="primary" class="content-area"> <main id="main" class="site-main"> <?php if ( have_posts() ) : ?> <header class="page-header"> <?php the_archive_title( '<h1 class="page-title">', '</h1>' ); ?> </header> <div class="postArchive" id="filterablePosts"> <?php $terms = get_terms( [ 'taxonomy' => 'post_tag', 'hide_empty' => true, 'fields' => 'names' ] ); if(!empty($terms)): ?> <div class="postArchive__filters"> Filter: <ul class="postArchive__filterList filters"> <li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''" aria-controls="postArchive__posts">All</button></li> <?php foreach($terms as $term): ?> <li class="filters__item"> <button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $term; ?>'}" @click="tag = '<?php echo $term; ?>'" aria-controls="postArchive__posts"><?php echo $term; ?></button> </li> <?php endforeach; ?> </ul> <p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p> </div> <?php endif; ?> <div class="postArchive__posts"> <?php // Start the Loop. while ( have_posts() ) : the_post(); ?> <article id="post-<?php the_ID(); ?>" <?php post_class(); ?> v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""' > <header class="entry-header"> <h2><?php the_title(); ?></h2> </header> <div class="entry-content"> <?php the_excerpt(); ?> </div> </article> <?php endwhile; // End the loop. ?> </div> </div> <?php // If no content, include the "No posts found" template. else : get_template_part( 'template-parts/content/content', 'none' ); endif; ?> </main> </section> <?php get_footer();Hereâs a working example on CodePen
See the Pen
Dynamic List Filtering in Vue using Server-side data fetching by Dan Brellis (@danbrellis)
on CodePen.
You may have noticed that I added in an aria-live="polite" notifier below the filter button list to let assistive tech users know the content has changed.
<p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p>To get the postCount Vue variable, we add some extra JavaScript to our Vue component:
new Vue({ el: '#filterablePosts', data: { 'tag': '', 'postCount': '' }, methods: { getCount: function(){ let posts = this.$el.getElementsByTagName('article'); return posts.length; } }, beforeMount: function(){ this.postCount = this.getCount(); }, updated: function(){ this.postCount = this.getCount(); } });</p>The new method getCount is used to select the article elements in our component div and return the length. Before the Vue component mounts we get the count to add to our new Vue postCount variable. Then, when the component updates after the user selects a tag, we get the count again and update our variable.
WhoCanUse
There are loads of microsites and developer tools for looking at color accessibility, including tools built right into browser DevTools. They often show you if a color passes AA or AAA WCAG guidelines. But color contrast is more complicated than that because there is a wide variety of vision impairments.
This site from Corey Ginnivan takes two colors and then shows you a simulation of different vision types (e.g. Tritanomaly, trouble distinguishing blues) and whether the colors still pass OK. Plus, situational events, like whether a screen is in direct sunlight.
Direct Link to Article — Permalink
The post WhoCanUse appeared first on CSS-Tricks.
The New Klim Type Website is Impossibly Lovely
Iâve spent the last hour hunched over the new Klim Type foundry website with my arms outstretched as if it was a fire in a very dark cave. Klim Type makes and sells wondrous fonts â like Tiempos, and National 2 or Pitch â and this fresh redesign now showcases them in all their glory. Hereâs an example of the type specimen from the Calibre typeface:
Thereâs a shocking amount of beautiful design in this little website and I particularly like the blog where Kris Sowersby recently wrote a wonderful essay about the design of their latest type release, Söhne, which looks into the design of the New York subway signage, too:
Direct Link to Article — Permalink
The post The New Klim Type Website is Impossibly Lovely appeared first on CSS-Tricks.
Dark Mode Favicons
Oooo! A bonafide trick from Thomas Steiner. Chrome will soon be supporting SVG favicons (e.g. <link rel="icon" href="/icon.svg">). And you can embed CSS within an SVG with a <style> element. That CSS can use a perfers-color-sceme media query, and as a result, a favicon that supports dark mode!
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"> <style> circle { fill: yellow; stroke: black; stroke-width: 3px; } @media (prefers-color-scheme: dark) { circle { fill: black; stroke: yellow; } } </style> <circle cx="50" cy="50" r="47"/> </svg>Safari also supports SVG, but it's different...
<link rel="mask-icon" href="/favicon.svg" color="#990000">You specify the color, so there is no opportunity there for a dark mode situation. A little surprising, since Apple is so all-in on this dark mode stuff. I have no idea if they plan to address that or what.
The post Dark Mode Favicons appeared first on CSS-Tricks.