Posts in the Hacks Category

Table Column Alignment with Variable Transforms

Published 9 months, 1 week past

One of the bigger challenges of recreating The Effects of Nuclear Weapons for the Web was its tables.  It was easy enough to turn tab-separated text and numbers into table markup, but the column alignment almost broke me.

To illustrate what I mean, here are just a few examples of columns that had to be aligned.

A few of the many tables in the book and their fascinating column alignments.  (Hover/focus this figure to start a cyclic animation fading some alignment lines in and out. Sorry if that doesn’t work for you, mobile readers.)

At first I naïvely thought, “No worries, I can right- or left-align most of these columns and figure out the rest later.”  But then I looked at the centered column headings, and how the column contents were essentially centered on the headings while having their own internal horizontal alignment logic, and realized all my dreams of simple fixes were naught but ashes.

My next thought was to put blank spacer columns between the columns of visible content, since table layout doesn’t honor the gap property, and then set a fixed width for various columns.  I really didn’t like all the empty-cell spam that would require, even with liberal application of the rowspan attribute, and it felt overly fragile  —  any shifts in font face (say, on an older or niche system) might cause layout upset within the visible columns, such as wrapping content that shouldn’t be wrapped or content overlapping other content.  I felt like there was a better answer.

I also thought about segregating every number and symbol (including decimal separators) into separate columns, like this:

<tr>
  <th>Neutrinos from fission products</th>
  <td>10</td> 
  <td></td>
  <td></td>
</tr>
<tr class="total">
  <th>Total energy per fission</th>
  <td>200</td>
  <td>±</td>
  <td>6</td>
</tr>

Then I contemplated what that would do to screen readers and the document structure in general, and after the nausea subsided, I decided to look elsewhere.

It was at that point I thought about using spacer <span>s.  Like, anywhere I needed some space next to text in order to move it to one side or the other, I’d throw in something like one of these:

<span class="spacer"></span>
<span style="display: inline; width: 2ch;"></span>

Again, the markup spam repulsed me, but there was the kernel of an idea in there… and when I combined it with the truism “CSS doesn’t care what you expect elements to look or act like”, I’d hit upon my solution.

Let’s return to Table 1.43, which I used as an illustration in the announcement post.  It’s shown here in its not-aligned and aligned states, with borders added to the table-cell elements.

Table 1.43 before and after the cells are shifted to make their contents visually align.

This is exactly the same table, only with cells shifted to one side or another in the second case.  To make this happen, I first set up a series of CSS rules:

figure.table .lp1 {transform: translateX(0.5ch);}
figure.table .lp2 {transform: translateX(1ch);}
figure.table .lp3 {transform: translateX(1.5ch);}
figure.table .lp4 {transform: translateX(2ch);}
figure.table .lp5 {transform: translateX(2.5ch);}

figure.table .rp1 {transform: translateX(-0.5ch);}
figure.table .rp2 {transform: translateX(-1ch);}

For a given class, the table cell is translated along the X axis by the declared number of ch units.  Yes, that means the table cells sharing a column no longer actually sit in the column.  No, I don’t care — and neither, as I said, does CSS.

I chose the labels lp and rp for “left pad” and “right pad”, in part as a callback to the left-pad debacle of yore even though it has basically nothing to do with what I’m doing here.  (Many of my class names are private jokes to myself.  We take our pleasures where we can.)  The number in each class name represents the number of “characters” to pad, which here increment by half-ch measures.  Since I was trying to move things by characters, using the unit that looks like it’s a character measure (even though it really isn’t) made sense to me.

With those rules set up, I could add simple classes to table cells that needed to be shifted, like so:

<td class="lp3">5 ± 0.5</td>

<td class="rp2">10</td>

That was most of the solution, but it turned out to not be quite enough.  See, things like decimal places and commas aren’t as wide as the numbers surrounding them, and sometimes that was enough to prevent a specific cell from being able to line up with the rest of its column.  There were also situations where the data cells could all be aligned with each other, but were unacceptably offset from the column header, which was nearly always centered.

So I decided to calc() the crap out of this to add the flexibility a custom property can provide.  First, I set a sitewide variable:

body {
	--offset: 0ch;
}

I then added that variable to the various transforms:

figure.table .lp1 {transform: translateX(calc(0.5ch + var(--offset)));}
figure.table .lp2 {transform: translateX(calc(1ch   + var(--offset)));}
figure.table .lp3 {transform: translateX(calc(1.5ch + var(--offset)));}
figure.table .lp4 {transform: translateX(calc(2ch   + var(--offset)));}
figure.table .lp5 {transform: translateX(calc(2.5ch + var(--offset)));}

figure.table .rp1 {transform: translateX(calc(-0.5ch + var(--offset)));}
figure.table .rp2 {transform: translateX(calc(-1ch   + var(--offset)));}

Why use a variable at all?  Because it allows me to define offsets specific to a given table, or even specific to certain table cells within a table.  Consider the styles embedded along with Table 3.66:

#tbl3-66 tbody tr:first-child td:nth-child(1),
#tbl3-66 tbody td:nth-child(7) {
	--offset: 0.25ch;
}
#tbl3-66 tbody td:nth-child(4) {
	--offset: 0.1ch;	
}

Yeah. The first cell of the first row and the seventh cell of every row in the table body needed to be shoved over an extra quarter-ch, and the fourth cell in every table-body row (under the heading “Sp”) got a tenth-ch nudge.  You can judge the results for yourself.

So, in the end, I needed only sprinkle class names around table markup where needed, and add a little extra offset via a custom property that I could scope to exactly where needed.  Sure, the whole setup is hackier than a panel of professional political pundits, but it works, and to my mind, it beats the alternatives.

I’d have been a lot happier if I could have aligned some of the columns on a specific character.  I think I still would have needed the left- and right-pad approach, but there were a lot of columns where I could have reduced or eliminated all the classes.  A quarter-century ago, HTML 4 had this capability, in that you could write:

<COLGROUP>
	<COL>
	<COL>
	<COL align="±">
</COLGROUP>

CSS2 was also given this power via text-align, where you could give it a string value in order to specify horizontal alignment.

But browsers never really supported these features, even if some of them do still have bugs open on the issue.  (I chuckle aridly every time I go there and see “Opened 24 years ago” a few lines above “Status: NEW”.)  I know it’s not top of anybody’s wish list, but I wouldn’t mind seeing that capability return, somehow. Maybe as something that could be used in Grid column tracks as well as table columns.

I also found myself really pining for the ability to use attr() here, which would have allowed me to drop the classes and use data-* attributes on the table cells to say how far to shift them.  I could even have dropped the offset variable.  Instead, it could have looked something like this:

<td data-pad="3.25">5 ± 0.5</td>

<td data-pad="-1.9">10</td>

figure.table *[data-pad] {transform: translateX(attr(data-pad,'ch'));}

Alas, attr() is confined to the content property, and the idea of letting it be used more widely remains unrealized.

Anyway, that was my journey into recreating mid-20th-Century table column alignment on the Web.  It’s true that sufficiently old browsers won’t get the fancy alignment due to not supporting custom properties or calc(), but the data will all still be there.  It just won’t have the very specific column alignment, that’s all.  Hooray for progressive enhancement!


Highlighting Accessible Twitter Content

Published 2 years, 4 months past

For my 2020 holiday break, I decided to get more serious about supporting the use of alternative text on Twitter.  I try to be rigorous about adding descriptive text to my images, GIFs, and videos, but I want to be more conscientious about not spreading inaccessible content through my retweets.

The thing is, Twitter doesn’t make it obvious whether someone else’s content has been described, and the way it structures (if I can reasonably use that word) its content makes it annoyingly difficult to conduct element or accessibility-property inspections.  So, in keeping with the design principles that underlie both the Web and CSS, I decided to take matters into my own hands.  Which is to say, I wrote a user stylesheet.

I started out by calling out things that lacked useful alt text.  It went something like this:

div[aria-label="Image"]::before {
	content: "WARNING: no useful ALT text";
	background: yellow;
	border: 0.5em solid red;
	border-radius: 1em;
	box-shadow: 0 0 0.5em black;
	…

…and so on, layering on some sizing, font stuff, and positioning to hopefully place the text where it would be visible.  This failed to satisfy for two reasons:

  1. Because of the way Twitter nests it dozens of repeatedly utility-classes divs, and the styles repeatedly applied thereby, many images were tall (but cut off) or wide (ditto) in ways that pulled the positioned generated text out of the visible frame shown on the site.  There wasn’t an easily-found human-readable predictable way to address the element I wanted to use as a positioning context.  So that was a problem.
  2. Almost every image in my feed had a big red and yellow WARNING on it, which quickly depressed me.

What I realized was that rather than calling out the failures, I needed to highlight the successes.  So I commented out the Big Red Angry Text approach above and got a lot more simple.

div[aria-label="Image"] {
	filter: grayscale(1) contrast(0.5);
}
div[aria-label="Image"]:hover {
	filter: none;
}

A screenshot of the author’s Twitter timeline, showing three tweets.  The middle tweet is text only.  The top tweet has a blurred-out image which is grayscale and of reduced contrast, indicating it has no useful alternative text.  The bottom tweet is also blurred, but it full color and contrast, indicating it has been given useful alternative text by the person who posted it.
Three consecutive tweets from my timeline on Friday, January 1st, 2021.  The blurring of the images in the top and bottom tweets is an effect of the Data Saver preference, not my CSS.

Just that.  De-emphasize the images that have the default alt text by way of their enclosing divs, and remove that effect on hover so I can see the image as intended if I so choose.  I like this approach better because it de-emphasizes images that aren’t properly described, while those which are described get a visual pop.  They stand out as lush islands in a flat sea.

In case you’ve been wondering why I’m selecting divs instead of img and video elements, it’s because I use the Data Saver setting on Twitter, which requires me to click on an image or video to load it.  (You can set it via Settings > Accessibility, display and languages > Data usage > Data saver.  It’s also what’s blurring the images in the screenshot shown here.)  I enable this setting to reduce network load, but also to give me an extra layer of protection when disturbing images and videos circulate.  I generally follow people who are careful about not sharing disturbing content, but I sometimes go wandering outside my main timeline, and never know what I’ll find out there.

After some source digging, I discovered a decent way to select non-described videos, which I combined with the existing image styles:

div[aria-label="Image"],
div[aria-label="Embedded video"] {
	filter: grayscale(1) contrast(0.5);
}
div[aria-label="Image"]:hover,
div[aria-label="Embedded video"]:hover {
	filter: none;
}

The fun part is, Twitter’s architecture spits out nested divs with that same ARIA label for videos, which I imagine could be annoying to people using screen readers.  Besides that, it also has the effect of applying the filter twice, which means videos that haven’t been described get their contrast double-reduced!  And their grayscale double-enforced!  Fun.

What I didn’t expect was that when I start playing a video, it loses the grayscale and contrast reduction effects even when not being hovered, which makes the second rule above a little over-written.  I don’t see the DOM structure changing a whole lot when the video loads and plays, so either videos are being treated differently for filter purposes, or I’m missing something in the DOM that’s invalidating the selector matching.  I might poke at it over time to find a fix, or I may just let it go.  The user experience isn’t too far off what I wanted anyway.

There is a gap in my coverage, which is GIFs pulled from Twitter’s GIF pool.  These have default alt text other than Image, which makes selecting for them next to impossible.  Just a few examples pulled from Firefox’s Accessibility panel when I searched the GIF panel for “this is a test”:

testing GIF
This Is ATest Fool GIF
Corona Test GIF by euronews
Test Fail GIF
Corona Virus GIF by guardian
Its ATest Josh Subdquist GIF
Corona Stay Home GIF by INTO ACTION
Is This A Test GIF
Stressed Out Community GIF
A1b2c3 GIF

I assume these are Giphy titles or something like that.  In nearly every case, they’re insufficient, if not misleading or outright useless.  I looked for markers in the DOM to be able to catch these, but didn’t find anything that was obviously useful.

I did think briefly about filtering for any aria-label that contains the string GIF ([aria-label*="GIF"]), but that would improperly catch images and videos that have been described but happen to have the string GIF inside them somewhere.  This might be a relatively rare occurrence, but I’m loth to gray out media that someone went to the effort of describing.  I may change my mind about this, but for now, I’m accepting that GIFs which appear in full color are probably not described, particularly when containing common memes, and will try to be careful.

I apply the above styles in Firefox using Stylus, which also available for Chrome, and they’re working pretty well for me.  I wish I could figure out a way to apply them in mobile contexts, but that’s a (much bigger) problem for another day.

I’m not the first to tread this ground, nor do I expect to be the last, sadly.  For a deeper dive into all the details of Twitter accessibility and the pitfalls that can occur, please read Adrian Roselli’s excellent article Improving Your Tweet Accessibility from just over two years ago.  And if you want apply accessibility-aid CSS to your own Twitter experience but can’t or won’t use Stylus, Adrian has a bookmarklet that injects Twitter alt text all set up and ready to go — you can use it as-is, or replace the CSS in his bookmarklet with mine above or your own if you want to take a different approach.

So that’s how I’m upping my awareness of accessible content on Twitter in 2021.  I’d love to hear what y’all are using to improve your own experiences, or links to tools and resources on this same topic.  If you have any of that, please drop the links in a comment below, so that everyone who reads this can benefit.  Thanks!


Polite Bash Commands

Published 2 years, 7 months past

For years, I’ve had a bash alias that re-runs the previous command via sudo.  This is useful in situations where I try to do a thing that requires root access, and I’m not root (because I am never root).  Rather than have to retype the whole thing with a sudo on the front, I just type please and it does that for me.  It looked like this in my .bashrc file:

alias please='sudo "$BASH" -c "$(history -p !!)"'

But then, the other day, I saw Kat Maddox’s tweet about how she aliases please straight to sudo, so to do things as root, she types please apt update, which is equivalent to sudo apt update.  Which is pretty great, and I want to do that!  Only, I already have that word aliased.

What to do?  A bash function!  After commenting out my old alias, here’s what I added to .bash_profile:

please() {
	if [ "$1" ]; then
		sudo $@
	else
		sudo "$BASH" -c "$(history -p !!)"
	fi
}

That way, if I remember to type please apachectl restart, as in Kat’s setup, it will ask for the root password and then execute the command as root; if I forget my manners and simply type apachectl restart, then when I’m told I don’t have privileges to do that, I just type please and the old behavior happens.  Best of both worlds!


Reply links in RSS items

Published 2 years, 8 months past

Inspired by Jonnie Hallman, I’ve added a couple of links to the bottom of RSS items here on meyerweb: a link to the commenting form on the post, and a mailto: link to send me an email reply.  I prefer that people comment, so that other readers can gain from the reply’s perspective, but not all comments are meant to be public.  Thus, the direct-mail option.

As Jonnie says, it would be ideal if all RSS readers just used the value of the author element (assuming it’s an email address) to provide an email action; if they did, I’d almost certainly add mine to my posts.  Absent that, adding a couple of links to the bottom of RSS items is a decent alternative.

Since the blog portion of meyerweb (and therefore its RSS) are powered by WordPress, I added these links programmatically via the functions.php file in the site’s theme.  It took me a bit to work out how to do that, so here it is in slightly simplified form (update: there’s an even more simplified and efficient version later in the post):

function add_contact_links ( $text ) {
   if (is_feed()) {
      $text .= '
      <hr><p><a href="' . get_permalink($post) . '#commentform">Add a comment to the post, or <a href="mailto:admin@example.com?subject=In%20reply%20to%20%22' . str_replace(' ', '%20', get_the_title()) . '%22">email a reply</a>.</p>';
   }
   return $text;
}
add_filter('the_content', 'add_contact_links');

If there’s a more efficient way to do that in WordPress, please leave a comment to tell the world, or email me if you just want me to know.  Though I’ll warn you, a truly better solution will likely get blogged here, so if you want credit, say so in the email.  Or just leave a comment!  You can even use Markdown to format your code snippets.


Update 2020-09-10: David Lynch shared a more efficient way to do this, using the WordPress hook the_feed_content instead of plain old the_content.  That removes the need for the is_feed() check, so the above becomes the more compact:

function add_contact_links ( $text ) {
   $text .= '
   <hr><p><a href="' . get_permalink($post) . '#commentform">Add a comment to the post, or <a href="mailto:admin@example.com?subject=In%20reply%20to%20%22' . str_replace(' ', '%20', get_the_title()) . '%22">email a reply</a>.</p>';
   return $text;
}
add_filter('the_content_feed', 'add_contact_links');

Thanks, David!


Firefox’s :screenshot command

Published 4 years, 9 months past

Back in 2015, I wrote about Firefox’s screenshot utility, which used to be a command in the GCLI.  Well, the GCLI is gone now, but the coders at Mozilla have brought command-line screenshotting back with :screenshot, currently available in Firefox Nightly and Firefox Dev Edition.  It’s available in the Web Console (⌥⌘K or Tools → Web Developer → Console).

An image I captured by typing :screenshot --dpr 0.5 in the Web Console

Once you’re in the Web Console, you can type :sc and then hit Tab to autocomplete :screenshot. From there, everything is the same as I wrote in 2015, with the exception that the --imgur and --chrome options no longer exist.  There are plans to add uploading to Firefox Screenshots as a replacement for the old Imgur option, but as of this writing, that’s still in the future.

So the list of :screenshot options as of late August 2018 is:

--clipboard
Copies the image to your OS clipboard for pasting into other programs.  Prevents saving to a file unless you use the --file option to force file-writing.
--delay
The time in seconds to wait before taking the screenshot; handy if you want to pop open a menu or invoke a hover state for the screenshot.  You can use any number, not just integers.
--dpr
The Device Pixel Ratio (DPR) of the captured image.  Values above 1 yield “zoomed-in” images; values below 1 create “zoomed-out“ results.  See the original article for more details.
--fullpage
Captures the entire page, not just the portion of the page visible in the browser’s viewport.  For unusually long (or wide) pages, this can cause problems like crashing, not capturing all of the page, or just failing to capture anything at all.
--selector
Accepts a CSS selector and captures only that element and its descendants.
--file
When true, forces writing of the captured image to a file, even if --clipboard is also being used.  Setting this to false doesn’t seem to have any effect.
--filename
Allows you to set a filename rather than accept the default.  Explicitly saying --filename seems to be optional; I find that writing simply :screenshot test yields a file called test.png, without the need to write :screenshot --filename test. YFFMV.

I do have one warning: if you capture an image to a filename like test.png, and then you capture to that same filename, the new image will overwrite the old image.  This can bite you if you’re using the up-arrow history scroll to capture images in quick succession, and forget to change the filename for each new capture.  If you don’t supply a filename, then the file’s name uses the pattern of your OS screen capture naming; e.g., Screen Shot 2018-08-23 at 16.44.41.png on my machine.

I still use :screenshot to this day, and I’m very happy to see it restored to the browser — thank you, Mozillans!  You’re the best.


Displaying CSS Breakpoint Information with Generated Content

Published 5 years, 3 months past

In the course of experimenting with an example design for my talks at An Event Apart this year, I came up with a way to keep track of which breakpoint was in force as I tested the design’s responsiveness.  I searched the web to see if anyone else had written about this and didn’t come up with any results, so I’ll document it here.  And probably also in the talks.

What I found was that, since I was setting breakpoints in ems instead of pixels, the responsive testing view in browsers didn’t really help, because I can’t maintain realtime mapping in my head from the current pixel value to however many rems it equals.  Since I don’t think the browser has a simple display of that information, I decided I’d do it myself.

It starts with some generated content:

body::before {content: "default";
   position: fixed; top: 1px; right: 1px; z-index: 100; padding: 1ch;
   background: rgba(0,0,0,0.67); color: rgba(255,255,255,0.75);
   font: bold 0.85rem Lucida Grande, sans-serif;}

You can of course change these to some other placement and appearance.  You can also attach these styles to the html element, or your page wrapper if you have one, or honestly even the footer of your document — since the position is fixed, it’ll be viewport-relative no matter where it originates.  The real point here is that we’re generating a bit of text we can change at each breakpoint, like so:

@media (max-width: 38em) {
   body::before {content: "<38em";}
   /* the rest of the breakpoint styles here */
}
@media (max-width: 50em) {
   body::before {content: "<50em";}
   /* the rest of the breakpoint styles here */
}
@media (min-width: 80em) {
   body::before {content: ">80em";}
   /* the rest of the breakpoint styles here */
}

The labels can be any string you want, so you can use “Narrow”, “Wide”, and so on just as easily as showing the measure in play, as I did.

The downside for me is that we automatically can’t make the labels cumulative in native CSS.  That means the order the @media blocks appear will determine which label is shown, even if multiple blocks are being applied.  As an example, given the styles above, at a width of 25em, the label shown will be <50em even though both the 38em and 50em blocks apply.

There are ways around this, like switching the order of the max-width blocks so the 38em block comes after the 50em block.  Or we could play specificity games:

@media (max-width: 38em) {
   html body::before {content: "<38em";}
   /* the rest of the breakpoint styles here */
}
@media (max-width: 50em) {
   body::before {content: "<50em";}
   /* the rest of the breakpoint styles here */
}

That’s not a solution that scales, sadly.  Probably better to sort the max-width media blocks in descending order, if you think you might end up with several.

The upside is that it’s easy to find and remove these lines once the development phase moves to QA.  Even better, before that point, you get a fully customizable in-viewport indication of where you are in the breakpoint stack as you look at the work in progress.  It’s pretty trivial to take this further by also changing the background color of the little box.  Maybe use a green for all the block above the “standard” set, and a red for all those below it.  Or toss in little background image icons of a phone or a desktop, if you have some handy.

So that’s the quick-and-dirty little responsive development hack I came up with this morning.  I hope it’s useful to some of you out there — and, if so, by all means share and enjoy!


Addendum: Emil Björklund proposes a variant approach that uses CSS Custom Properties (aka CSS variables) to implement this technique.


Generating Wireframe Boxes with CSS and HTML5

Published 5 years, 5 months past

I was recently noodling around with some new layout ideas for An Event Apart’s speaker pages (e.g., Chris Coyier’s or Jen Simmons’) and wanted to share the ideas with other members of the team.  But what I really wanted to show was wireframes to convey basic arrangement of the pieces, since I hadn’t yet done any time polishing details.

I thought about taking screenshots and Photoshopping wireframe boxes over the various layout pieces, but then I wondered: could I overlay boxes on the live page with CSS?  Or perhaps even create and overlay them with nothing but some declarations and a wanton disregard for the sensibilities of god or man?

And that’s when I realized…I could.

“Your scientists were so preoccupied with whether they could they didn’t stop to think if they should.”  — Dr. Ian Malcolm (Jurassic Park, 1993)

Now I’m going to share my discovery with you.

Before I get started, I want to make one thing clear: this isn’t backward compatible.  I don’t care.  It doesn’t need to be.  It does work in the latest versions of Firefox and Chrome, within reasonable tolerances — Chrome falls a bit short on one aspect, which I’ll point out when we get there.

All good?  Then let’s go.

The goal was creating X-filled boxes that wireframers love so very, very much.  I figured, any container element that needs to have a box stuck over it gets a class of wireframe.

<div class="wireframe">…(content goes here)…</div>

(Don’t get too attached to that class, by the way: it doesn’t survive the article.  Foreshadowing!)

The easy part was drawing a box around any element with that class.  I decided to use outlines, because they’re rarely employed for box edging and they don’t affect the layout even if your box-sizing is set to content-box.  (Mine usually is, by dint of not setting box-sizing at all.  But, you know, you do you.)

.wireframe {outline: 2px solid gray;}
Adding simple boxes (to a redesigned local copy of Rachel Andrew’s speaker page)

The boxes overlap each other because the layout pieces on the right are, at least for the moment, floated.  They’re laid out that way so that if the right-hand content is short and the bio and articles run long, they can wrap around below the ‘sidebar’.  It’s generally useful to have the outlines showing the actual limits of the element boxes to which they’re attached.

There is a potential drawback here: if your layout involves using negative margins to pull some elements out of their parents, and those parent elements are designated as wireframe boxes, outlines will stretch around the outhanging elements in Firefox, though not in Chrome.  Borders do not act the same way in Firefox.  I can’t rightly call this a bug, because I’m honestly not sure what outlines should do here.  Just be aware of it, is what I’m saying.

Anyway, drawing rectangles with outlines, that’s the easy part.  Now I needed two diagonal lines, going from corner to corner.  But how?

Linear gradients, that’s how.  See, if you use quadrant-based directions for your gradients, special magic math happens under the hood such that at the exact midpoint of the gradient, the color-line that extends perpendicularly off the gradient ray shoots precisely into the corners of the two quadrants adjacent to the quadrant into which the gradient ray is pointing.  Okay, that was probably hard to follow.  For example, set the gradient direction as to top right and the 50% color line of the gradient will run into the top left to the bottom right corners.

That’s exactly what I needed!  Thus:

.wireframe {outline: 2px solid gray;
   background:
      linear-gradient(to top right,
         transparent 49.9%, gray 49.9%,
         gray 50.1%, transparent 50.1%);
}

Okay, that’s one diagonal line.  The other is literally a copy of the first, except for having it go toward a different quadrant.

.wireframe {outline: 2px solid gray;
   background:
      linear-gradient(to top right,
         transparent 49.9%, gray 49.9%,
         gray 50.1%, transparent 50.1%),
      linear-gradient(to bottom right,
         transparent 49.9%, gray 49.9%,
         gray 50.1%, transparent 50.1%);
}
Diagonal lines!

Bingo: an X.  But not one that scales terribly well.  Using percentages there means that the gray lines will be as thick as 0.2% the total length of the gradient ray.  Small boxes get thin, sometimes broken diagonals.  Great big boxes get thick honkin’ lines.

So I fixed it with calc().

.wireframe {outline: 2px solid gray;
   background:
      linear-gradient(to top right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      linear-gradient(to bottom right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px));
}
Properly sized diagonal lines!

And there you go: two-pixel-thick diagonal lines.

There are two things to note here, before we move on.  First is that the spaces around the operators in the calc() values are intentional and, more to the point, necessary.  If you remove one or both of those spaces, calc() will simply fail to work.  In other words, calc(50%-1px) will fail — no background for you!  This is as designed, and there are reasons for it I don’t want to go into here, but suffice to say they exist and are arguably sensible.  calc(50% - 1px), on the other hand, works as intended.

Well, mostly: this is where Chrome comes up a bit short.  In large boxes, Chrome creates fuzzy lines thicker than 2 pixels.  I’m not sure what it’s doing to fudge the numbers here, but it sure seems like it’s fudging something.  The lines also don’t go into the corners quite as precisely as they should.  Firefox’s lines, on the other hand, come out correctly sized no matter what box size I set up, even if they are a bit jagged at times, and they go exactly into the corners of all the boxes I tested.  Chrome’s sloppiness here isn’t a deal-breaker, as far as I’m concerned, but it’s there and you should know about it.

So that’s an element with an outer edge and two diagonal lines.  This is great as long as the box contains no actual content, which will sit on top of the diagonals, as you can see with the “hero image” in the top right.  Maybe that’s what you want, in which case great, but I specifically wanted overlays.  That way I could stick them on a live page and sort of fade out the contents while sticking wireframe boxes on top, to make the boxes the focus while still showing the stuff inside them.

Enter generated content.  If I create a pseudo-element and attach the diagonal background gradients to that, they can sit on top of all the content.  That’ll also let me throw in a translucent background color to fill the box and fade out the contents.  Like so:

.wireframe {outline: 2px solid gray;
   position: relative; z-index: 1;}
.wireframe::before {
   position: absolute; z-index: 8675309;
   top: 0; bottom: 0; right: 0; left: 0;
   background:
      linear-gradient(to top right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      linear-gradient(to bottom right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      #FFF9;
      content: "";
}
Placing the diagonals, and a translucent color, over the elements

I used ::before mostly because hey, why not, but also because clearfix is usually an ::after and I hear people are still using clearfix, more’s the pity.  So this avoids it.  If you’ve moved beyond the need for clearfix, then you can use ::after just as easily.  Whatever floats your fancy.  (Get it?  Floats?  Yeah?  Clearfix?  Floats?  Ah, I kill me.)

The stupidly large z-index on the ::before is there to put the box overlay above any gridded, flexed, or positioned content that has an automatic z-index, or at least a sensible one.  You can raise it as high as you’d like (and your browser’s bit-depth will allow).  The small z-index on the elements themselves, on the other hand, makes sure they get an explicit stacking placement instead of an automatically-assigned place on the Z axis.  I find this generally settles a number of odd behaviors in various browsers.  Your experience may vary.

It was at this point that I realized there was a whole other level here.  I mean, wireframe boxes stretched over content is pretty nifty all by itself.  That could have been enough.  But it wasn’t.

Because what I realized was that I didn’t just want wireframe boxes, I wanted labeled wireframe boxes.  If a box was being applied to a list of articles, then I wanted a great big “Articles” label sitting in the middle of it, to make it obvious what was being placed there.

Well, there was already a content property just sitting there, waiting to throw in actual content instead of an empty string, but how to fill it?  And that’s when I knew that .wireframe’s days were numbered.

That’s because the easiest way to label each box was to use an HTML data attribute to attach the label I wanted to display.  And once that attribute was there, why not apply the wireframe styles based on the presence of the attribute, instead of adding class names that might get in the way of some unexpected DOM script?  So I changed the markup and CSS like this:

<div data-wf="Articles">…(content goes here)…</div>
[data-wf] {outline: 2px solid gray;
   position: relative; z-index: 1;}
[data-wf]::before {
   position: absolute; z-index: 8675309;
   top: 0; bottom: 0; right: 0; left: 0;
   background:
      linear-gradient(to top right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      linear-gradient(to bottom right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      #FFF9;
      content: "";
}

(True story: I almost called it data-wtf instead.  Almost.)

Having done that, I could modify the CSS to insert the attribute value, style the inserted text to look nice, and use flexbox properties to center it in the box.  So I did.

[data-wf] {outline: 2px solid gray;
   position: relative; z-index: 1;}
[data-wf]::before {
   position: absolute; z-index: 8675309;
   top: 0; bottom: 0; right: 0; left: 0;
   background:
      linear-gradient(to top right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      linear-gradient(to bottom right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      #FFF9;
      content: attr(data-wf);
      font: bold 2em Jubilat, Georgia, serif;
      color: gray;
      display: flex; justify-content: center; align-items: center;
}
Adding centered labels

That yielded big beautiful bold Jubilat labels (Jubilat is one of AEA’s brand font faces), sitting right on top of the center of the box, the crossing of the two diagonal lines behind them.

Which actually turned out to be a small problem for me.  That text is certainly readable, but I wanted it to stand out a bit more from the diagonals.  I decided to stack text shadows in order to semi-simulate outside text stroking.

[data-wf] {outline: 2px solid gray;
   position: relative; z-index: 1;}
[data-wf]::before {
   position: absolute; z-index: 8675309;
   top: 0; bottom: 0; right: 0; left: 0;
   background:
      linear-gradient(to top right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      linear-gradient(to bottom right,
         transparent calc(50% - 1px), gray calc(50% - 1px),
         gray calc(50% + 1px), transparent calc(50% + 1px)),
      #FFF9;
      content: attr(data-wf);
      font: bold 2em Jubilat, Georgia, serif;
      color: gray;
      text-shadow:
         0 0 0.25em #FFF9, 0 0 0.25em #FFF9,
         0 0 0.25em #FFF9, 0 0 0.25em #FFF9,
         0 0 0.25em #FFF9;
      display: flex; justify-content: center; align-items: center;
}
“Stroking” the box labels

It’s possible to use just four offset shadows with minimal or zero blur, but I find it sometimes creates weird jags on serif fonts, so I like stacking blurred shadows better.  But, again, you do you.

As I looked over the results, it slowly dawned on me that the white-on-gray box scheme works well enough for a starting wireframe setup with no branding applied, but I was planning to drop these on pages with actual design and colors and that sort of thing.  I didn’t want the boxes to fill with translucent white; I wanted them to be translucent versions of the page background color.  And, furthermore, I wanted a way to be able to easily alter that color, when applied to different designs.

Custom properties to the rescue!  Which is to say, native CSS variables to the rescue!

Our page background color at An Event Apart is #F7F6F1, a combo I actually have memorized at this point.  Since I wanted to fill the boxes with a roughly three-quarters-opaque variant, I settled on #F7F6F1BB.  (Actual 75% is BF, if you care.)  So I defined a custom property for it:

html {
   --fill: #F7F6F1BB;
}

I could have assigned the variable to the [data-wf] rule instead of html, but I felt like setting them globally.  Because that’s how I roll, yo — Wulf & Shaw have no strings on me.  If you want to bring the variables in closer, go for it.

While I was there, I figured, why not, let’s also define a variable for the shared color of the outlines, diagonals, and label text.

html {
   --fill: #F7F6F1BB;
   --wire: gray;
}

Then all I needed was to sprinkle variable calls where the colors were sitting.  I ended up here:

html {
   --fill: #F7F6F1BB;
   --wire: gray;
}
[data-wf] {outline: 2px solid var(--wire);
   position: relative; z-index: 1;}
[data-wf]::before {
   position: absolute; z-index: 8675309;
   top: 0; bottom: 0; right: 0; left: 0;
   background:
      linear-gradient(to top right,
         transparent calc(50% - 1px), var(--wire) calc(50% - 1px),
         var(--wire) calc(50% + 1px), transparent calc(50% + 1px)),
      linear-gradient(to bottom right,
         transparent calc(50% - 1px), var(--wire) calc(50% - 1px),
         var(--wire) calc(50% + 1px), transparent calc(50% + 1px)),
      var(--fill);
      content: attr(data-wf);
      font: bold 2em Jubilat, Georgia, serif;
      color: var(--wire);
      text-shadow:
         0 0 0.25em var(--fill), 0 0 0.25em var(--fill),
         0 0 0.25em var(--fill), 0 0 0.25em var(--fill),
         0 0 0.25em var(--fill);
      display: flex; justify-content: center; align-items: center;
}
The final product, with color themes and everything

The label could be broken out to use its own variable (e.g., --text or --label) easily enough, but I wanted a minimum of things to change.  I know myself too well to set up a bunch of controls to fiddle with, especially where color is concerned.

And with that, I had a ready-to-hand, easily theme-able wireframing style block that I can drop into any development page and invoke simply by adding a few data attributes to the markup.  Data attributes, I might add, that would be trivially easy to later find and remove with regular expressions.  It’s a quick way to make it clear to stakeholders that a work in progress is, in fact, in progress, as well as a handy way to visualize which pieces of a prototype layout are going where.

Now that we’ve come to the end and you’re still hanging in there with me, let me just say that I hope you’ve enjoyed this little trip through various parts of CSS.  If you have any questions, feel free to drop them in the comments below.  I’ll do my best to respond in reasonable amounts of time, travel and such permitting.


P.S. And one final note, as Kai Ryssdal would say: every bit of CSS I used here is covered in CSS: The Definitive Guide, 4th Edition.  I turned to my print copy twice in the process of working all this out, as it happens, to remind myself of specific syntax (for custom properties) and whitespace requirements (for calc() operators).  It really feels good to have a thing I made be useful to me!


Essential Tool: Firefox’s screenshot Command

Published 7 years, 7 months past

The Graphical Command Line Interpreter (GCLI) has been removed from Firefox, but screenshot has been revived as :screenshot in the Web Console (⌥⌘K), with most of the same options discussed below. Thus, portions of this article have become incorrect as of August 2018.  Read about the changes in my post Firefox’s :screenshot Command.

Everyone has their own idiosyncratic collection of tools they can’t work without, and I’ve recently been using one of mine as I produce figures for CSS: The Definitve Guide, Fourth Edition (CSS:TDG4e).  It’s Firefox’s command-line screenshot utility.

To get access to screenshot, you first have to hit ⇧F2 for the Developer Toolbar, not ⌥⌘K for the Web Console.  (I know, two command lines — who thought that was a good idea?  Moving on.)  Once you’re in the Developer Toolbar, you can type s and then hit Tab to autocomplete screenshot.  Then type a filename for your screenshot, if you want to define it, either with or without the file extension; otherwise you’ll get whatever naming convention your computer uses for screen captures.  For example, mine does something like Screen Shot 2015-10-22 at 10.05.51.png by default.  If you hit [return] (or equivalent) at this point, it’ll save the screenshot to your Downloads folder (or equivalent).  Done!

Except, don’t do that yet, because what really makes screenshot great is its options; in my case, they’re what elevate screenshot from useful to essential, and what set it apart from any screen-capture addon I’ve ever seen.

The option I use a lot, particularly when grabbing images of web sites for my talks, is --fullpage.  That option captures absolutely everything on the page, even the parts you can’t see in the browser window.  See, by default, when you use screenshot, it only shows you the portion of the page visible in the browser window.  In many cases, that’s all you want or need, but for the times you want it all, --fullpage is there for you.  Any time you see me do a long scroll of a web page in a talk, like I did right at the ten-minute mark of my talk at Fluent 2015, it was thanks to --fullpage.

If you want the browser --chrome to show around your screenshot, though, you can’t capture the --fullpage.  Firefox will just ignore the -fullpage option if you invoke --chrome, and give you the visible portion of the page surrounded by your browser chrome, including all your addon icons and unread tabs.  Which makes some sense, I admit, but part of me wishes someone had gone to the effort of adding code to redraw the chrome all the way around a --fullpage capture if you asked for it.

Now, for the purposes of CSS:TDG4e’s figures, there are two screenshot options that I cannot live without.

A screen capture of Facebook’s “Trending” panel.
I captured this using screenshot fb-trend --selector '#u_0_l'.  That saved exactly what you see to fb-trend.png.

The first is --selector, which lets you supply a CSS selector to an element — at which point, Firefox will capture just that element and its descendants.  The only, and quite understandable, limitation is that the selector you supply must match a single element.  For me, that’s usually just --selector 'body', since every figure I create is a single page, and there’s nothing in the body except what I want to include in the figure.  So instead of trying to drag-select a region of the screen with ⇧⌘4, or (worse) trying to precisely size the browser window to show just the body element and not one pixel more, I can enter something like screenshot fig047 --selector 'body' and get precisely what I need.

That might seem like a lot to type every time, but the thing is, I don’t have to: not only does the Web Toolbar have full tab-autocomplete, the Toolbar also offers up-arrow history.  So once I’ve tab-completed the command to capture my first figure, I just use the up arrow to bring the command back and change the file name.  Quick, simple, efficient.

The second essential option for me is --dpr, which defines a device pixel ratio.  Let’s say I want to capture something at four times the usual resolution.  --dpr 4 makes it happen.  Since all my figures are meant to go to print as well as ebooks, I can capture at print-worthy resolutions without having to use ⌘+ to blow up the content, or fiddle with using CSS to make everything bigger.  Also if I want to go the other way and capture a deliberately pixellated version of a page, I can use something like --dpr 0.33.

I have used this occasionally to size down an image online: I “View Image” to get it in its own window, then use screenshot with a fractional DPR value to shrink it.  Yes, this is a rare use case, even for me, but hey — the option exists!  I haven’t used the DPR option for my talks, but given the growing use of HD 16:9 projectors — something we’ve been using at An Event Apart for a while now, actually — I’m starting to lean toward using --dpr 2 to get sharper images.

(Aside: it turns out this option is only present in very recent versions of Firefox, such as Developer Edition 43 and the current Nightlies.  So if you need DPR, grab a Nightly and go crazy!)

A closeup of text on a test page.
A snippet of an image I captured using --dpr 5.  On-screen, the page was at 100% zoom, 16-pixel (browser default) text sizing.  The resulting capture was 4000×2403 pixels.

And that’s not all!  You can set a --delay in seconds, to make sure a popup menu or other bit of interaction is visible before the capture happens.  If you want to take your captured image straight into another program before saving it, there’s --clipboard.  And there’s an option to upload straight to --imgur, though I confess I haven’t figured out how that one works.  I suspect you have to be logged into imgur first.  If anyone knows, please leave a comment so the rest of us know how to use it!

The one thing that irks me a little bit about screenshot is that the file name must come before the options.  When I’m producing a bunch of figures in a row, having to drag-select just the file name for replacement is a touch tedious; I wish I could put the file name at the end of the command, so I could quickly drag-select it with a rightward wrist-flick.  But all things considered, this is a pretty minor gripe.  Well, shut my mouth and paint me red — it turns out you can put the filename after the options.  Either that wasn’t possible at some point and I never retested the assertion, or it was always possible and I just messed up.  Either way, this irk is irksome no more!

The other thing I wish screenshot could do is let me define a precise width or height in pixels — or, since I’m dreaming, a value using any valid CSS length unit — and scale the result to that measure.  This isn’t really useful for the CSS:TDG4e figures, but it could come in pretty handy for creating talk slides.  No, I have no idea how that would interact with the DPR option, but I’d certainly be willing to find out.

So that’s one of my “unusual but essential” tools.  What’s yours?


Browse the Archive

Earlier Entries