Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

Doing an Inset Drop-shadow With SVG Filters

Last updated:

Using SVG Filters can be really powerful, but also pretty complicated. In particular, SVG can do drop shadows better than CSS's box-shadow property, because it can respond to the transparency of whatever you're doing, rather than only shadowing the element's box.

However, the drop-shadow filter is not a primitive: it's built up out of the simpler filter effects, and it's fairly complex:

 <filter id="drop-shadow">
  <feGaussianBlur in="[alpha-channel-of-input]" stdDeviation="[radius]"/>
  <feOffset dx="[offset-x]" dy="[offset-y]" result="offsetblur"/>
  <feFlood flood-color="[color]"/>
  <feComposite in2="offsetblur" operator="in"/>
  <feMerge>
    <feMergeNode/>
    <feMergeNode in="[input-image]"/>
  </feMerge>
</filter> 

This only does a plain drop-shadow, where the element is casting a shadow below it. Modifying it to do an inset shadow, where the element is "cut out" and the light source is casting a shadow into it, isn't hard, but you have have to know what all the little bits of the original shadow are doing.

Let's get the important bit out of the way. Here's a working inset drop-shadow with SVG filters:

And here's the code for it:

<!DOCTYPE html>
<svg style="border: thin solid;">
   <filter id="inset-shadow" x="-50%" y="-50%" width="200%" height="200%">
    <feComponentTransfer in=SourceAlpha>
      <feFuncA type="table" tableValues="1 0" />
    </feComponentTransfer>
    <feGaussianBlur stdDeviation="3"/>
    <feOffset dx="5" dy="5" result="offsetblur"/>
    <feFlood flood-color="rgb(20, 0, 0)" result="color"/>
    <feComposite in2="offsetblur" operator="in"/>
    <feComposite in2="SourceAlpha" operator="in" />
    <feMerge>
      <feMergeNode in="SourceGraphic" />
      <feMergeNode />
    </feMerge>
  </filter> 
  <circle cx=50 cy=50 r=20 fill=red filter="url(#inset-shadow)" />
</svg>

Let's walk through this, node by node.

  1. First, set up the filter region with the x/y/width/height attributes. By default, the entire filtering operation takes place in a box 10% larger in every direction than the source graphic. Since we're working with blurs, which can extend a good bit away from the source, we need a bit more spaaaaaace. 50% larger in all directions is generally fine.

  2. <feComponentTransfer> We start by taking the alpha channel of the source graphic with in="SourceAlpha", and inverting it. The plain drop-shadow uses the graphic's normal alpha channel, blurring and offsetting it to produce the shadow. Here, the source graphic itself is not casting the shadow - everything else is - so we need to invert its alpha. This gives us an image that is transparent where the image is solid, and solid where the image is transparent, which we'll turn into the shadow.

  3. <feGaussianBlur> Now we blur that image, preparing it for shadow-ness, and then use <feOffset> to shift it a bit, so the light source will appear to come from the side a bit.

    This is also the first time we see the result attribute. By default, each sibling filter effect takes the output of the previous filter effect as its input. If you need do something complicated, though, you can name the output of a particular filter effect, and refer to it explicitly later.

  4. The next two elements are somewhat non-obvious. When we grabbed the "SourceAlpha", it gave us a solid-black image with the same alpha channel as the source image. If we want a black shadow, that works fine, but if we want to color it, we need to do some work. In this case, we use <feFlood> to generate an infinite field of color (my example just uses black again, whatever), and then, the important bit, uses <feComposite operator="in"> to chop it down to just the bits that overlap the blurred shadow.

    There are a bunch of composite operations, but "in", in particular, basically multiplies the alpha channel of the first image (in this case, implicitly the result of the previous <feFlood> effect) with the alpha channel of the second image (explicitly pointing to the offsetblur result). It's like using a stencil, where the solid parts of the in2 image let the in image show through, and the transparent parts block it.

    This gives us a blur of the right color. If you just want a black shadow, you can drop this <feFlood> and the first <feComposite>, as the blur you generated previously starts out black.

  5. We then do another <feComposite operator="in">, this time using the SourceAlpha again as the stencil mask. This cuts out most of the blur, leaving us with only the parts that will overlap the source image.

  6. Finally, we merge the shadow and the source image with <feMerge> and the predefined SourceImage value, putting the shadow second so it goes on top.

And we're done! When you reference this filter, it produces an inset drop shadow over your image.

Jenn Schiffer's Biographies on Medium (are amazing)

Last updated:

How JavaScript is Going to Replace Node.js

Jenn Schiffer is the Pulitzer prize-winning author The Joy-ent In Forgetting, The Joy-ent In Acceptance, an O’Reilly guide to merit-based promotions in the tech workplace.


Say Ello To My Little Friend

Jenn Schiffer is a cat behaviorist by day, and a journalismist by night.


Work, Work, and Work Some More for Little Benefit to Yourself

Jenn Schiffer almost bought a Taco Bell in early 2014 because sometimes it’s good to have a backup plan in case you leave the industry.


The Hassle of Haskell

Jenn Schiffer is a neapolitan JavaScript developer who is good in math. Like, she took a lot of math classes beyond calculus in college and grad school. And she uses math. She also took a class about the history of rap and rock and she loves rap and rock.


I’m moving to Medium

Jenn Schiffer has never written for Wired, Newsweek, or the Rolling Stones, but neither has Ernest Hemmingway - so stick that in your resume, Steven Levy.

*Editor’s note: Jenn is not moving to Medium and a restraining order is in progress.


Putting an End to Net Neutrality

,Jenn Schiffer is a web developer who thinks comma-first in JavaScript code looks ridiculous.


A Controversial Unboxing of Firefox’s new D.R.M.

Jenn Schiffer literally used to work at a large media/entertainment company, so — although she does not like the idea of DRM — she knows that the decisions made by these media companies to require it are not lovers of open source and data like we are, but are instead worried about missing out on financial gain from total control of content even after purchase, regardless of how irrational and selfish that fear may be. Therefore, open source developers find themselves reaching a fork in the road that horses them to make controversial compromises so that they can continue our mission to promote the open web but at the same time cater to users who do not know or care about the mission of promoting an open web, instead of fading into obscurity in vain. Dealing with stubborn, ignorant, rich people in power is one of the hardest problems in computer science.


Deadly Declarations of the Web Pathologist

Jenn Schiffer used to skateboard off rails, but now she develops SEO and Google-juice-strong Rales apps.


What Makes a Developer a *Real* Developer

Jenn Schiffer is a house-flipper and amateur photographer in the .NET community. She has written the Pulitzer-prize winning biography of Dr. Joan Merriam-Webster, entitled Talk Wordy to Me.


More Efficienter JavaScript

Jenn Schiffer is a government contracted code optimizer and hobbyist wood craftswoman in central New Jersey.


Introducing Web Components and What It Means for Search Engine Optimization and Privacy

Jenn Schiffer is an SEO evangelist, meta keyword developer, and organizer of #SEOconf #NewJersey.


Eschew IDEs and Text Editors for Pure Close-to-the-Metal Web Development

Jenn Schiffer is a web developer and was profiled in O’Reilly and Vogue’s Haute’st 30 Devs Under 30 of 2013.


Task Runners and Builders: Just Because You Can Does That Mean You Should?

Jenn Schiffer is a web developer and growth hacker focused on workflow productivity and paradigm shifting the game changing industry of C.S.S. preprocessing.


Jokes as a Disservice

Jenn Schiffer is a serious, real developer and well-known Eclipse user. She will be protesting outside of DHTMLConf.


A Case Against Conference Codes of Conduct

Jenn Schiffer is an unemployed brunette Ellie Goulding impersonator. You will not find her at a conference which does not have a Code of Conduct.


A Case Against Bitcoin

Jenn Schiffer studied economics and is the tech evangelist for a think-tank that explores past currencies like Planet Lunch Points and pogs.


This 1 Neat Trick For Pixel Perfect Web Pages

Jenn Schiffer is a Business Guy™ and net Award winner for her Dart fanfiction collection, A List ADart.


Why I’m Breaking Up with jQuery and Getting Back Together with PHP

Jenn Schiffer is a father, husband, Christian.


A Call For Web Developers To Deprecate Their CSS

Jenn Schiffer is a web developer and computational complexity theorist. She is the writer of “PHP: The Hood Parts” and other programmer-themed urban fiction available on Amazon Kindle and the Zune app store.


A Client-side Responsive Image Solution, Finally

Jenn Schiffer, recently named one of the Top 9 People who Were Once Age 9 in Technology by Forbes, is a full-stack web developer and writer.


An Introduction to Two-factor In-Browser CSS Preprocessing

Jenn Schiffer is the best-selling author of the adult thriller How CSS Killed RSS. Her biological father, Nick Ortenzio, brainstormed with her on this very important concept during lunch yesterday at Tokyo Buffet, the official venue of the Boolean Operator Consortium.


Optimizing Knowledge Intake Using Half-Life Growth Hacking Techniques

Jon Shiffer is a #growthhacker and technical editor of the Pulitzer Prize winning book, “PHP is Better Than CSS.” He teaches pickup artists all over America how to code, but what he really needs to learn, himself, is how to love.


How Node.js is Going to Replace JavaScript

Jenn Schiffer is the Pulitzer prize-winning author of PHP is Better Than CSS. She lives in a box with her dog, Lucifer.


I Want a Thing, And I Want It Immediately

Jenn Schiffer is a celebrity plagiarist who likes things, and she’s worth it.


Solving the Cross-Browser Dilemma

Steve Ballmer is a Parkour hobbyist and is passionate about open Web technologies.


Here is the exact solution to a programming problem you’re searching about on Google

Jenn Schiffer is a serial entrepreneur, tech blogger, and social media guru/evangelist/disrupter.


wowowow gifs

No bio this time, but the header gif is amazing:

Successful business meeting, concluding with a slow pan to creeper stare


Top 5 C.S.S. Mistakes To Avoid Before You Die

Jenn Schiffer is a hobbyist hot dog photographer.


Open Letter to the Guy Who Dumped jQuery via a Medium Post, from jQuery

No bio this time, but whole post reproduced here:

Uhhh, I’m pregnant.

— j “$” Query


You Are CORGI-ALLY Invited to the Biggest Tech Event of the Year

(hey jenn, before you send this out to the mailing list, can you make sure that it looks really cool and web 2.0, like emboss the images or add a starry background, it should be pretty straight-forward and easy to do. thanks, you’re the best!)


5 Cloud Servers You Need to See Before You Die

Jenn Schiffer does not work at CNN.


The Case Against Semantic Classes and IDs in HTML

Jenn is a fucking programmer, and this is all a true story.


Next-level Lean Startups

Jeff Bezos is a paradigm-shifting programmer/entrepreneur who is totally over Tina, but, like, if you’re reading this, Tina, hit me up. Yeah, cool.


Become the Best Web Developer You Can Be in 1 Simple Step

Mark Zuckerberg is an 11x web developer and Editor of netmag with a h34rt of g01d.


Why Web Designers Are The Reason Your Parents Divorced

This was a sponsored post about a sponsored post. (Original post was removed when everybody hated it.)


How Your Cat is Doing Tech Startups Wrong

No bio, but entire post was a single gif:

Shibe is best at web design


Minutes from the Boolean Consortium Meeting

Minutes taken by both members of the Boolean Consortium.


10 Boring Things You Need to Know This Morning in Tech

Jenn Schiffer is Chief Justice of CSS Perverts. A million candybars for James Plafke for being the inspiration of this important new column.


Hide Your Deepest Darkest Secrets From the NSA

Jenn Schiffer is the award-winning body double of both LeBron James and Mark Zuckerberg, and a woman in technology.


What To Expect When Working With a Woman in Technology

No bio, but post is hilarious, go read it.


Tab Atkins believes that if he calls what's he's doing "curating", people will associate him with the funniness of the content he's scraping.

Why is the Hue Circle... Circular?

Last updated:

As you may know from following this blog, I occasionally get obsessed with the math and science of colors. I've been interested in this subject my entire life; it's a perfect blend of subjective experience (color vision) with objective logic (color spaces, color addition, etc). Plus, I'm colorblind (deuteranopic - I can't see green as well as an average person), so it always held an extra frisson of interest for me.

One thing always bothered me as a kid, though, and it took me until only a year or two ago before I finally figured it out: why is the EM spectrum (where all the colors live) a line, while hue is generally represented as a circle? What is the reason that blue "wraps around" to red in every art book, when it doesn't seem to happen in reality? Even rainbows disagree - they go red->purple and then stop.

To answer this properly, we'll have to lay down some groundwork.

Cone Response Curves

Our color vision comes from the cone cells in our retina. We have three of them, each specialized at seeing a particular color. All of our light-sensing is accomplished by nerves that contain a particular chemical from the opsin family which changes shape when it absorbs photons of a particular wavelength. The nerve detects this shape change, and sends the information off to the brain - "I've been hit!".

We have four different opsin molecules in our eyes - rhodopsin in our rod cells, which responds to most of the visual light spectrum, and three specialized photopsins in each of our cone cells, which respond to a much narrower slice of the spectrum. Each one responds most strongly to one particular wavelength, and responds more weakly as the photon it absorbs moves away from that target wavelength, with one peaking at roughly a "red" color, one peaking at a "yellow-green", and one peaking at a "blue". They each overlap significantly, so a photon that triggers one type of cone will also trigger the other cones, to some degree.

Response curves for our four vision cells

All of our color vision comes from the amount of nerve response we get from these three types of cells. If our brain receives a strong red response, a medium green response, and a weak blue, it knows it's looking at red. If red and green are both moderately high, but blue is still weak, it's looking at yellow. (The wavelength of yellow light lies between red and green, so it's near the peaks of both red and green cone response curves.) Etc.

Spectral Colors

Now we know enough to talk about spectral vs non-spectral colors. A spectral color is the color we see when light of a single visible wavelength hits our eye. For example, if we shine light with a wavelength of about 560nm, you'll see it as yellow.

A non-spectral color, on the other hand, is any color we see that isn't created by a single wavelength of light, but rather by a combination. For example, white is produced by receiving light of multiple wavelengths across the spectrum, so all the cones get activated roughly the same amount; no single wavelength of light can ever produce that kind of response.

Non-spectral colors don't really "exist", in terms of "what colors exist in the light spectrum", but they often mimic spectral colors. For example, if you shine a combination of red and green light, your eyes will record a roughly equally high response from the red and green cones, and a low response from the blue cones. This is exactly the same as the response that spectral yellow produces, so this combination (red + green) gives us non-spectral yellow. This is how you see yellow on your computer monitors, which only have red, green, and blue pixels. Non-spectral colors aren't precisely the same (the green light component of non-spectral yellow, for example, activates the blue cones a little bit more than spectral yellow does), but it's close enough to fool our eyes most of the time.

In fact, you can produce non-spectral mimics of all the spectral colors between the peak of the red curve and the peak of the blue curve, by just using two lights, one with a slightly higher wavelength and one with a slightly lower wavelength. You can think of it as "averaging" the two wavelengths. (You can probably produce mimics a bit further out from each with some effort, but it would be more complicated.)

Now What About Purple?

So, now we can phrase our question a bit better - why does spectral purple (wavelength ~400nm) look like non-spectral purple (combination of ~440nm and ~650nm)? The "averaging" explanation doesn't make any sense; it tells us that we should be seeing some sort of non-spectral green. (Actually, the "averaging" explanation only works for wavelengths that are relatively close - blue and red are too far apart for it to work.) So what's the deal?

Most response-curve graphs you find are fairly simplistic. They show each cone's responses as a simple hill - smoothly climbs, peaks, then smoothly descends to zero. The graph I posted earlier in this post, though, shows a slightly more accurate view. It's not super-obvious, but the red and green cones' responses jump up a little bit again, over in the blue-light region. This means that spectral purple doesn't just activate the blue cones, it also slightly activates the red and green cones (mostly the red, iiuc).

This finally explains why mixing blue and red light also looks purplish - it produces the same cone responses! In other words, non-spectral purple only exists through a weird quirk of biology, because your red and green-sensing cones have an irregular response curve; it's not inherent in the physics of light (unlike many of the other non-spectral colors, which the "averaging" argument generally explains).

This also explains why magenta doesn't show up in the rainbow. Spectral purple, at best, only produces a weak red response. It's impossible to get a spectral color that produces both a strong blue and a strong red response. That can only happen in non-spectral purple, where you can easily ramp up the red intensity and get a strong response. Probably about 2/3rds of the space between red and blue on hue circles is purely non-spectral.

So, finally, mystery solved. 10-year-old me would have been ecstatic to hear this kind of explanation, because I virtually never see this topic discussed in articles about color vision.

Postscript: Imaginary Colors

I've sometimes seen the phrase "imaginary color" used to refer to colors that only exist as non-spectral colors, which no single wavelength of light can possibly produce. Most of the "purple" range, then, is "imaginary".

("Imaginary color" is also sometimes used for a different concept that touches on another weird quirk of color vision: colors like "reddish green" are difficult or impossible to imagine. It can also describe colors like "supergreen" that you can only see by tricking your eyes. There's a biological reason for these!)

Unfortunately, humans don't have very many imaginary colors, with our three simple cones. They give us 7 broad categories of colors, based on what cones are most activated: high red, high green, high blue, high red+green, high green+blue, high red+blue, and high everything. Of these, only the last two are imaginary, giving us the maroons and the grays (including white). We just can't see any more - every other combination either produces something close to a spectral color, or moves outside of our visual spectrum entirely.

If we had more types of cones, though, we'd have more imaginaries, too. Most birds have a fourth cone that senses UV light, out past blue. This gives them several more imaginaries, such as red+UV and green+UV, plus several three-cone combinations. Pigeons are believed to have five cones, as do several insects, some eels, and a handful of other animals. And then, of course, there's the mantis shrimp, which has 16 types of cones, and can detect polarization of light.

If you gained new cones that peaked between your existing cones, some non-spectral colors would no longer mimic spectral ones. For example, if we had a yellow cone, red+green light would produce an imaginary color, rather than looking like spectral yellow, because our eyes would have different responses in the two situations.

This postscript is kinda rambly, so I guess my main point is: COLORS ARE COOL AND INTERESTING.

2014 State of Element Queries

Last updated:

About a year ago, I talked about Element Queries and why they were difficult to make work. It's about time for an update.

Short version: Not much has changed. Naive element queries still suffer from circularity issues, and there's been no real effort to specify anything to get around it. However, Shadow DOM seems well-placed as a way to get an EQ feature into the web.

What Are Element Queries Again?

An "element query" is like a media query, specifically the 'width'/'height' MQs, but they apply to the width/height of the element's container, rather than of the screen or device.

Why Were Element Queries Problematic Again?

Element Queries (let's just call them EQs from here on) suffer from basic circular dependency issues.

Here's a simple example: Say you have an element which is normally 100px wide, but it uses an EQ to say that if its container is less than 200px wide, the element becomes 500px wide. (This is silly, but bear with me.) If the container is explicitly sized (width: 150px; or the like), this is fine, but if the container is sized to its contents (float: left;, for example), then you have a circular dependency: if the element is 100px wide, the container is 100px wide, but that trigger the EQ, so the element is 500px wide, so the container is 500px wide, but that disables the EQ, so the element is 100px wide, so the container is 100px wide, etc.

What About Them <iframe>s?

A funny detail of this is that EQs already exist, at least in some form. If you use a MQ on a page displayed in an <iframe>, the MQ is resolved against the size of the <iframe> element. This is exactly an EQ, if you treat the <iframe> as the "container" in the above explanations.

This avoids the problems outlined above because an <iframe> cannot depend on the size of its contents. An <iframe> is only ever explicitly sized; if it doesn't have anything explicit, it defaults to being 300px wide and 150px tall. The contents never affect its size in any way, and so the circularity problem never comes into effect - if you get super wide because the <iframe> is skinny, nobody cares; the <iframe> just sprouts a scrollbar.

How Can We Get Around This?

Of course, you don't want to use <iframe>s all over your page. It would be nice if we could somehow establish the same sort of situation while keeping all our content in the page together.

The first thought people usually have is to create some CSS value, perhaps a display value or something like that, which makes the element no longer pay attention to its contents. Unfortunately, this doesn't work well - since you don't know which elements are "independent" until you apply CSS, you might do too much work on initial layouts before you figure out that you're all wrong. Also, since the boundaries aren't immediately clear (and in fact can shift over time, if the property starts/stops applying to various elements), it's somewhat difficult to understand what elements are allowed to use EQs.

Better would be something that would alert browsers to the "independent" nature of the element before they start applying CSS. For example, an HTML attribute would work for this. Unfortunately, it's annoying to have to sprinkle presentation-based attributes over your page just to get your styling to work right.

I think that we've got a good opportunity to make this less bad with Web Components, though. They already represent a useful "boundary" in the form of the shadow DOM, so the potential confusion of not knowing which element your EQ is applying to would go away - it always applies to your host element, assuming it's marked as "independent".

While Level 1 of the Shadow DOM specs are stabilizing right now, I think this is something reasonable to aim for as a Level 2 feature. You'd be able to set some switch when defining your custom element, or perhaps set it per ShadowRoot, which would make your component's layout independent of its contents, just like <iframe>. Then, EQs would work inside the shadow, referring to the size of the host element. Outside of a shadow, or in a non-"independent" shadow, EQs would just always be false.

So that's the state of Element Queries in 2014. We're not really any closer to having them than we were in 2013, but there's light on the horizon for a possible good solution.

Basic Category Theory Terms

Last updated:

Magma: A set of objects that can be "added" or "combined" in some way, so that if you add any two of them the result is also in the set. That's it.

Semigroup: A magma whose "addition" operation obeys associativity: (a + b) + c must give the same answer as a + (b + c). For example, strings form a semigroup with concatenation as their "addition" operator: ("foo" + "bar") + "baz" = "foobarbaz", etc. So do the integers under the min() operation.

Monoid: A semigroup with a "zero" element: something that, when "added" to any element in the group, produces that other element again. For example, the empty string is the "zero" element in the string/concat monoid. The integers with min() aren't a monoid, but they become one if you add infinity as the "zero" element. Lots of interesting things are monoids; it's a very simple and useful constraint to satisfy. If you satisfy "monoid", you get a free version of the reduce function using your operation.

Group: A monoid where every element has an opposite or "negative" version, so that if you "add" an element and its opposite, you get the "zero" element. Strings with concatenation are not a group - concatenation only ever produces larger strings, not smaller ones, so you can't ever take a string like "foo" and add another string to it to get the empty string. On the other hand, integer/addition is a group - the opposite of each member is just the negative integer corresponding to it.

An Alternate Path to a Group

There's an alternate way to get from magma to group. You add different properties along the way, but when they're all combined they add up to the same thing that the previous path did. However, these properties are harder for most things to satisfy, and so they're not generally as useful; most of the things you use that'll satisfy any of these do so because they're all the way to a group already.

Quasigroup: This one's a little subtle. It's a magma, but the operation has the property that you can transform any element of the set to any other element by combining it with an appropriate third element. It has to do so whether the first element is the left or right argument, but the element you choose to combine it with can be different in the two cases.

The integers with subtraction form a quasigroup - if I want to transform "2" into "5", I can do it with 2 − -3 = 5 and 7 − 2 = 5. (So do the integers with addition, but they satisfy so many of these definitions that they're not very interesting to talk about.) On the other hand, strings with concatenation are not a quasigroup - while you can transform some strings, like "foo" to "foobar", you can't generally do it both ways (nothing satisfies the equation X + "foo" = "foobar"), and there are some strings you can't transform between at all.

Loop: A quasigroup with a "zero" element, which transforms an element into itself regardless of which side it starts on. Integer/subtraction is not a loop: X − 0 = X, which is what we want, but 0 − X = -X, which isn't what we want (and no other number satisfies either side, obviously.) I can't come up with a good example of something that's a loop but not a group right now.

Group: Ah, back to group. Along this path, a group is a loop with associativity.

Going Further

Abelian or Commutative X: Any of the above types might have an operation which is commutative - it doesn't matter which order the arguments are. Integer/min is an abelian monoid, because min(1, 10) = 10 and min(10, 1) = 10.