Lea Verou’s blogWeb standards, Software Engineering, Product, Usability, and more2024-03-15T16:46:25Zhttps://lea.verou.me/Lea Veroulea@verou.meCheck whether a CSS property is supported2009-02-10T00:00:00Zhttps://lea.verou.me/?p=3<p>Sometimes when using JavaScript, you need to determine whether a certain CSS property is supported by the current browser or not. For instance when setting opacity for an element, you need to find out whether the property that the browser supports is <code>opacity</code>, <code>-moz-opacity</code> (<code>MozOpacity</code>), <code>-khtml-opacity</code> (<code>KhtmlOpacity</code>) or the IE proprietary <code>filter</code>.</p>
<p>Instead of performing a forwards <strong>in</strong>compatible browser detect, you can easily check which property is supported with a simple conditional. The only thing you’ll need is a DOM element that exists for sure. A DOM element that exists in every page and is also easily accessible via JS (no need for <code>getElementsByTagName</code>), is the <code>body</code> element, but you could use the <code><head></code> or even a <code><script></code> tag (since there is a script running in the page, a <code><script></code> tag surely exists). <strong>In this article we’ll use document.body, but it’s advised that you use the head or script elements, since document.body may not exist at the time your script is run.</strong></p>
<p>So, now that we have an element to test at, the test required is:</p>
<pre><code class="language-js">if('opacity' in document.body.style)
{
// do stuff
}
</code></pre>
<p>Of course you’d change <code>document.body</code> with a reference to the element you’d like to test at (in case it’s not the body tag) and <code>'opacity'</code> with the name of the actual property you want to test. You can even wrap up a function to use when you want to check about the support of a certain property:</p>
<pre><code class="language-js">function isPropertySupported(property)
{
return property in document.body.style;
}
</code></pre>
<p>The only thing you should pay attention to, is using the JavaScript version of the CSS property (for example <code>backgroundColor</code> instead of <code>background-color</code>)</p>
<p>Wasn’t it easy?</p>
JS library detector2009-02-11T00:00:00Zhttps://lea.verou.me/?p=15<p>Ever wondered which JavaScript library (if any) is hidden beneath the bells & whistles of each site you gazed at? Since I am a curious person, I find myself wondering every time, so after a bit of research, I wrapped up a little bookmarklet that instantly told me the answer every time.</p>
<p>The logic behind it is that every JavaScript library creates at least one global variable with an easily recognizable name. For most JavaScript libraries, this is simply their name (Prototype, jQuery, DOMAssistant, MooTools, dojo). For some others, its something close enough to their name (YAHOO for YUI, Scriptaculous for <a href="http://script.aculo.us/">script.aculo.us</a>, Ext for ExtJS). So if you check the precence of this global variable, you are effectively checking for the precence of the related framework. Most of them also contain a property with their version (which is usually named ‘version’ or ‘Version’ or ‘VERSION’ (in YUI)) - in fact the only library that did not contain such a property was DOMAssistant. So, after a sneak peek at their code, I could easily set up some conditionals that check whether a certain library exists in the page and if so, alert its name and version. If multiple libraries exist at the same page, multiple popups will appear.</p>
<p>So, here is the bookmarklet:</p>
<p><a href="javascript:if('Prototype'%20in%20window)%20{%20var%20ret%20=%20'Prototype%20'%20+%20Prototype.Version;%20if('Scriptaculous'%20in%20window)%20ret%20+=%20'%20with%20script.aculo.us%20'%20+%20Scriptaculous.Version;%20alert(ret);%20}%20if('jQuery'%20in%20window)%20alert('jQuery%20'%20+%20jQuery.fn.jquery);%20if('MooTools'%20in%20window)%20alert('MooTools%20'%20+%20MooTools.version);%20if('YAHOO'%20in%20window)%20alert('YUI%20'%20+%20YAHOO.VERSION);%20if('dojo'%20in%20window)%20alert('Dojo%20'%20+%20dojo.version);%20if('Ext'%20in%20window)%20alert('ExtJS%20'%20+%20Ext.version);%20if('DOMAssistant'%20in%20window)%20alert('DOMAssistant');" class="call-to-action">JS library detector</a></p>
<p>Just drag it to your bookmarks toolbar and it’s ready.</p>
<p>And here is the human-readable code:</p>
<pre><code class="language-js">if('Prototype' in window)
{
var ret = 'Prototype ' + Prototype.Version;
if('Scriptaculous' in window) ret += ' with script.aculo.us ' + Scriptaculous.Version;
alert(ret);
}
if('jQuery' in window) alert('jQuery ' + jQuery.fn.jquery);
if('MooTools' in window) alert('MooTools ' + MooTools.version);
if('YAHOO' in window) alert('YUI ' + YAHOO.VERSION);
if('dojo' in window) alert('Dojo ' + dojo.version);
if('Ext' in window) alert('ExtJS ' + Ext.version);
if('DOMAssistant' in window) alert('DOMAssistant');
</code></pre>
<p>Am I nuts? Certainly. Has it been useful to me? Absolutely.</p>
Extend Math.round, Math.ceil and Math.floor to allow for precision2009-02-12T00:00:00Zhttps://lea.verou.me/?p=27<p><code>Math.round</code>, <code>Math.ceil</code> and <code>Math.floor</code> are very useful functions. However, when using them, I find myself many times needing to specify a precision level. You don’t always want to round to an integer, you often just want to strip away <strong>some</strong> of the decimals.</p>
<p>We probably all know that if we have a function to round to integers, we can round to X decimals by doing <code>Math.round(num*Math.pow(10,X)) /</code> <code>Math.pow(10,X)</code>. This kind of duck typing can get tedious, so usually, you roll your own function to do that. However, why not just add that extra functionality to the functions that already exist and you’re accustomed to?</p>
<p>Let’s start with <code>Math.round</code>. It’s the most needed one anyway.</p>
<p>Firstly we’ll have to store the native function somewhere, since we’re going to replace it. So we do something along the lines of:</p>
<pre><code class="language-js">Math._round = Math.round;
</code></pre>
<p>Now let’s <strong>sigh</strong> replace the native <code>Math.round</code> with our own:</p>
<pre><code class="language-js">Math.round = function(number, precision)
{
precision = Math.abs(parseInt(precision)) || 0;
var coefficient = Math.pow(10, precision);
return Math._round(number*coefficient)/coefficient;
}
</code></pre>
<p>And guess what? It still works the old way too, so your old scripts won’t break.</p>
<p>So now, let’s go to <code>Math.ceil</code> and <code>Math.floor</code>. If you notice, the only thing that changes is the function name. Everything else is the same. So, even though we could copy-paste the code above and change the names, we would end up with triple the size of the code that we need and we would have also violated the <a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself" title="Don't Repeat Yourself">DRY</a> principle. So we could put the names of the functions in an array, and loop over it instead:</p>
<pre><code class="language-js">(function(){
var MathFns = ['round', 'floor', 'ceil' ];
for(var i = MathFns.length; i>-1; i--)
{
Math['_' + MathFns[i]] = Math[MathFns[i]];
Math[MathFns[i]] = function(number, precision)
{
precision = Math.abs(parseInt(precision)) || 0;
var coefficient = Math.pow(10, precision);
return Math['_' + MathFns[i]](number*coefficient)/coefficient;
}
}
})();
</code></pre>
<p>Why the closure? To allow us to be free in defining our variables without polluting the global namespace. In case <code>Array.prototype.forEach()</code> was cross-browser or if you have mutated the <code>Array</code> prototype to add it for non-supporting ones, you could easily do that:</p>
<pre><code class="language-js">['round', 'floor', 'ceil' ].forEach(function(funcName){
Math['_' + funcName] = Math[funcName];
Math[funcName] = function(number, precision)
{
precision = Math.abs(parseInt(precision)) || 0;
var coefficient = Math.pow(10, precision);
return Math['_' + funcName](number*coefficient)/coefficient;
}
});
</code></pre>
<p>No closures and much easier to read code.</p>
<p>However, nothing comes without a cost. In this case, the cost is performance. In my tests, the new function takes about twice the time of the native one. Adding a conditional to check if the precision is falsy and use the native function directly if so, doesn’t improve the results much, and it would slow the function down for precision values > 0. Of course the speed would be just as much if the function was a normal one and not a replacement for Math[something], that doesn’t have anything to do with it.</p>
Find the vendor prefix of the current browser2009-02-13T00:00:00Zhttps://lea.verou.me/?p=48<p>As you probably know already, when browsers implement an experimental or proprietary CSS property, they prefix it with their “vendor prefix”, so that 1) it doesn’t collide with other properties and 2) you can choose whether to use it or not in that particular browser, since it’s support might be wrong or incomplete.</p>
<p>When writing CSS you probably just include all properties and rest in peace, since browsers ignore properties they don’t know. However, when changing a style via javascript it’s quite a waste to do that.</p>
<p>Instead of iterating over all possible vendor prefixes every time to test if a prefixed version of a specific property is supported, we can create a function that returns the current browser’s prefix and caches the result, so that no redundant iterations are performed afterwards. How can we create such a function though?</p>
<h3 id="things-to-consider" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/#things-to-consider">Things to consider</a></h3>
<ol>
<li>The way CSS properties are converted their JS counterparts: Every character after a dash is capitalized, and all others are lowercase. The only exception is the new <code>-ms-</code> prefixed properties: Microsoft did it again and made their JS counterparts start with a lowercase <code>m</code>!</li>
<li>Vendor prefixes always start with a dash and end with a dash</li>
<li>Normal CSS properties never start with a dash</li>
</ol>
<h3 id="algorithm" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/#algorithm">Algorithm</a></h3>
<ol>
<li>Iterate over all supported properties and find one that starts with a known prefix.</li>
<li>Return the prefix.</li>
<li>If no property that starts with a known prefix was found, return the empty string.</li>
</ol>
<h3 id="javascript-code" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/#javascript-code">JavaScript code</a></h3>
<pre><code class="language-js">function getVendorPrefix()
{
var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/;
var someScript = document.getElementsByTagName('script')[0];
for(var prop in someScript.style)
{
if(regex.test(prop))
{
// test is faster than match, so it's better to perform
// that on the lot and match only when necessary
return prop.match(regex)[0];
}
}
// Nothing found so far?
return '';
}
</code></pre>
<p><strong>Caution:</strong> Don’t try to use someScript.style.hasOwnProperty(prop). It’s missing on purpose, since if these properties aren’t set on the particular element, hasOwnProperty will return false and the property will not be checked.</p>
<h3 id="browser-bugs" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/#browser-bugs">Browser bugs</a></h3>
<p>In a perfect world we would be done by now. However, if you try running it in Webkit based browsers, you will notice that the empty string is returned. This is because for some reason, Webkit does not enumerate over empty CSS properties. To solve this, we’d have to check for the support of a property that exists in all webkit-based browsers. This property should be one of the oldest -webkit-something properties that were implemented in the browser, so that our function returns correct results for as old browser versions as possible. <code>-webkit-opacity</code> seems like a good candidate but I’d appreciate any better or more well-documented picks. We’d also have to test <code>-khtml-opacity</code> as <a href="http://webkit.org/blog/22/css3-goodies-borders-and-backgrounds/#comment-121">it seems that Safari had the -khtml- prefix before the -webkit- prefix</a>. So the updated code would be:</p>
<pre><code class="language-js">function getVendorPrefix()
{
var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/;
var someScript = document.getElementsByTagName('script')[0];
for(var prop in someScript.style)
{
if(regex.test(prop))
{
// test is faster than match, so it's better to perform
// that on the lot and match only when necessary
return prop.match(regex)[0];
}
}
// Nothing found so far? Webkit does not enumerate over the CSS properties of the style object.
// However (prop in style) returns the correct value, so we'll have to test for
// the precence of a specific property
if('WebkitOpacity' in someScript.style) return 'Webkit';
if('KhtmlOpacity' in someScript.style) return 'Khtml';
return '';
}
</code></pre>
<p>By the way, if Webkit ever fixes that bug, the result will be returned straight from the loop, since we have added the Webkit prefix in the regexp as well.</p>
<h3 id="performance-improvements" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/#performance-improvements">Performance improvements</a></h3>
<p>There is no need for all this code to run every time the function is called. The vendor prefix does not change, especially during the session :P Consequently, we can cache the result after the first time, and return the cached value afterwards:</p>
<pre><code class="language-js">function getVendorPrefix()
{
if('result' in arguments.callee) return arguments.callee.result;
var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/;
var someScript = document.getElementsByTagName('script')[0];
for(var prop in someScript.style)
{
if(regex.test(prop))
{
// test is faster than match, so it's better to perform
// that on the lot and match only when necessary
return arguments.callee.result = prop.match(regex)[0];
}
}
// Nothing found so far? Webkit does not enumerate over the CSS properties of the style object.
// However (prop in style) returns the correct value, so we'll have to test for
// the precence of a specific property
if('WebkitOpacity' in someScript.style) return arguments.callee.result = 'Webkit';
if('KhtmlOpacity' in someScript.style) return arguments.callee.result = 'Khtml';
return arguments.callee.result = '';
}
</code></pre>
<h3 id="afterthoughts" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/#afterthoughts">Afterthoughts</a></h3>
<ul>
<li>Please don’t use this as a browser detection function! Apart from the fact that browser detects are a bad way to code 99.9% of the time, it’s also unreliable for IE, since Microsoft added a vendor prefix in IE8 only. Before that it followed the classic attitude “We have a large market share so standards and conventions don’t apply to us”.</li>
<li>There are some browsers that support multiple prefixes. If that is crucial for you, you may want to return an array with all prefixes instead of a string. It shouldn’t be difficult to alter the code above to do that. I’ll only inform you that from my tests, Opera also has <code>Apple</code>, <code>Xn</code> and <code>Wap</code> prefixes and Safari and Chrome also have <code>Khtml</code>.</li>
<li>I wish there was a list somewhere with ALL vendor prefixes… If you know such a page, please leave a comment.</li>
</ul>
CSS3 border-radius, today2009-02-13T00:00:00Zhttps://lea.verou.me/?p=40<p>This is the first one from a series of articles I’m going to write about <em>using CSS3 properties or values <strong>today</strong></em>. I’ll cover everything I have found out while using them, including various browser quirks and bugs I know of or have personally filed regarding them. In this part I’ll discuss ways to create rounded corners without images and if possible without JavaScript in the most cross-browser fashion.</p>
<p>I will not cover irregular curves in this article, since I’ve yet to find any person who actually needed them, even once, including myself and browser support for them is far worse.</p>
<p><strong>Caution:</strong> The contents of a container with border-radius set are NOT clipped according to the border radius in any implementation/workaround mentioned below, and no, setting overflow to hidden won’t help (and even if it did, you’d risk text missing). You should specify a proper border-radius and/or padding to them if you want them to follow their container’s curves properly. This could allow for some nice effects but most of the time it’s just a pain in the a$$.</p>
<h3 id="mozilla-firefox" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/css3-border-radius-today/#mozilla-firefox">Mozilla Firefox</a></h3>
<p>Firefox supports rounded corners since version 2. However incomplete support in version 2 made designers sceptical to use them. The problem was that the rounded corners created were aliased back then, and also did not crop the background image, so if you had one, no rounded corners for you. This was fixed in FF3, so now more and more designers are starting to use them. The syntax is</p>
<pre><code>-moz-border-radius: [Number][unit];
</code></pre>
<p>This is effectively a shorthand for:</p>
<pre><code>-moz-border-radius-bottomleft: [Number][unit];
-moz-border-radius-bottomright: [Number][unit];
-moz-border-radius-topleft: [Number][unit];
-moz-border-radius-topright: [Number][unit];
</code></pre>
<p>You don’t need to specify all these properties though, even if you wan’t different measures per corner, as <code>-moz-border-radius</code> functions as a regular CSS shorthand, allowing us to specify all 4 corners at once. It can be used in the following ways:</p>
<pre><code>-moz-border-radius: [Top-left and Bottom-right] [Top-right and bottom-left];
-moz-border-radius: [Top-left] [Top-right and bottom-left] [Bottom-right];
-moz-border-radius: [Top-left] [Top-right] [Bottom-right] [Bottom-left];
</code></pre>
<p>A good mnemonic rule for the order of the values is that they are arranged clockwise, starting from Top left.</p>
<h3 id="apple-safari" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/css3-border-radius-today/#apple-safari">Apple Safari</a></h3>
<p>Safari also implements CSS3 border-radius, but in a quite different way. If you want to set all four corners to the same border-radius, the process is almost identical. The only thing needed is:</p>
<pre><code>-webkit-border-radius: [Number][unit]
</code></pre>
<p>However, things start to get tricky when you want to specify different radiuses per corner. Webkit does not support a shorthand syntax, since it chose to implement the spec closely, sacrifycing clarity but allowing for more flexibility. To cut a long story short, <a href="http://www.css3.info/border-radius-apple-vs-mozilla/" title="Read more about the difference between Mozilla's and Webkit's implementations">Webkit supports irregular curves instead of just circle quarters on each corner</a>, so if you try to add 2 values, the result will be <a href="http://www.css3.info/wp-content/uploads/2007/06/border-radius.png">horrendous</a>.</p>
<p>So, you have to specify all four properties (or less if you want some of them to be square). To make matters even worse, the way the names of the properties are structured is different. There is one more dash, and the position of the corner styled by each property is not at the end but before <em>-radius</em>:</p>
<pre><code>-webkit-border-top-left-radius
-webkit-border-top-right-radius
-webkit-border-bottom-left-radius
-webkit-border-bottom-right-radius
</code></pre>
<p><strong>Caution:</strong> If the dimensions of your element are not enough to accomodate the rounded corners, they will be square in Webkit-based browsers. Specify a <code>min-width</code>/<code>min-height</code> or enough padding to avoid this.</p>
<h3 id="google-chrome" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/css3-border-radius-today/#google-chrome">Google Chrome</a></h3>
<p>Since Google Chrome is based on Webkit, its border-radius support is like Safari’s. However, it’s haunted by an ugly bug: It renders the rounded corners <strong>aliased</strong>. :(</p>
<h3 id="opera" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/css3-border-radius-today/#opera">Opera</a></h3>
<p>The bad news is that Opera does not implement the CSS3 border-radius yet (it will in the future, <a href="http://twitter.com/dstorey/status/1177020119">confirmed</a>). The good news is that it allows for SVG backgrounds since version 9.5. The even better news is that it supports <code>data://</code> URIs, so you can embed the SVG in your CSS, without resorting to external files as <a href="http://ejohn.org/blog/talk-performance-improvements-in-browsers/#comment-345434">someone recently pointed out to me</a>. <a href="http://a.deveria.com/">Alexis Deveria</a> was clever enough to even <a href="http://a.deveria.com/roundgen/">create a generator for them</a>, so that you could easily specify the background, border width and border-color and get the data URI instantly. This is a quite useful tool, but lacks some features (for instance you might want the background to be semi-transparent, like the one used in this blog). It’s ok for most cases though.</p>
<p>While Opera’s current lack of border-radius support is disappointing, you can utilize it pretty well with this method and if you know SVG well enough yourself you can create stunning effects.</p>
<h3 id="internet-explorer-(aka-%E2%80%9Cthe-web-designer%E2%80%99s-nemesis%E2%80%9D)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/css3-border-radius-today/#internet-explorer-(aka-%E2%80%9Cthe-web-designer%E2%80%99s-nemesis%E2%80%9D)">Internet Explorer (aka “The Web designer’s nemesis”)</a></h3>
<p>There’s no need to tell you that IE doesn’t support border-radius or SVG backgrounds, even in it’s latest version, right? You probably guessed already. There is some hope here though, a clever guy named <a href="http://dillerdesign.wordpress.com/">Drew Diller</a> carefully researched the MS-proprietary <a href="http://en.wikipedia.org/wiki/Vector_Markup_Language">VML language</a> and came up with <a href="http://www.dillerdesign.com/experiment/DD_roundies/">a script that utilizes it to create rounded corners in IE</a>. The bad news is that MS when releasing IE8 fixed some things and messed up others, so the script barely works on it. It also has some other <a href="http://www.dillerdesign.com/experiment/DD_roundies/#lacking">shortcomings</a>, but for most cases it can be a great tool (for IE7 and below, unless MS surprises us and fixes the VML regressions in IE8 before the stable). Also, if rounded corners are not crucial to your design and you don’t get too much traffic from IE users, you might consider ignoring IE altogether and having square corners in it. This way you’re also serving the greater good, since when IE users see your site in a supporting browser, they’ll conclude that “Oh, this browser shows the web nicer!” and the site will still be just as usable (in most cases rounded corners are not that crucial for usability, although <a href="http://www.usabilitypost.com/2008/09/24/the-function-of-rounded-corners/">they enchance it a bit</a>).</p>
<h3 id="afterword" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/css3-border-radius-today/#afterword">Afterword</a></h3>
<p>I hope this article helped you learn something new. If you found any mistakes or inaccuracies, don’t hesitate to leave a comment, I don’t know everything and I’m not god. :)</p>
<p>One thing I have in mind is creating a PHP script that takes care of all these incompatibilities for you and caches the result. I don’t know if I’ll ever find the time to write it though, especially before someone else does :P</p>
Bulletproof, cross-browser RGBA backgrounds, today2009-02-15T00:00:00Zhttps://lea.verou.me/?p=70<p><strong>UPDATE:</strong> <a href="http://lea.verou.me/rgba.php/"><strong>New version</strong></a></p>
<p>First of all, happy Valentine’s day for yersterday. :) This is the second part of my “<a href="http://lea.verou.me/2009/02/css3-border-radius-today/">Using CSS3 today</a>” series. This article discusses current RGBA browser support and ways to use RGBA backgrounds in non-supporting browsers. Bonus gift: A PHP script of mine that creates fallback 1-pixel images on the fly that allow you to easily utilize RGBA backgrounds in any browser that can support png transparency. In addition, the images created are forced to be cached by the client and they are saved on the server’s hard drive for higher performance.</p>
<h3 id="browsers-that-currently-support-rgba" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#browsers-that-currently-support-rgba">Browsers that currently support RGBA</a></h3>
<p>These are:</p>
<ul>
<li>Firefox 3+</li>
<li>Safari 2+</li>
<li>Opera 10 (still in beta)</li>
<li>Google Chrome</li>
</ul>
<p>In these browsers you can write CSS declarations like:</p>
<p>background: rgba(255,200,35,0.5) url(somebackground.png) repeat-x 0 50%;
border: 1px solid rgba(0,0,0,0.3);
color: rgba(255,255,255,0.8);</p>
<p>And they will work flawlessly.</p>
<h3 id="internet-explorer" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#internet-explorer">Internet Explorer</a></h3>
<p>Surprisingly, it seems that <a href="http://www.hedgerwow.com/360/dhtml/rgba/demo.php">Internet Explorer supported RGBA backgrounds long before the others</a>. Of course, with it’s <a href="http://msdn.microsoft.com/en-us/library/ms532997.aspx">very own properietary syntax</a>, as usual:</p>
<p>filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#550000FF, endColorstr=#550000FF);</p>
<p>And since nothing is ever simple with IE, IE8 <a href="http://blogs.msdn.com/ie/archive/2008/09/08/microsoft-css-vendor-extensions.aspx">requires a special syntax which has to be put before the first one</a> to work properly in IE8 beta1:</p>
<p>-ms-filter: “progid:DXImageTransform.Microsoft.gradient(startColorstr=#550000FF, endColorstr=#550000FF)”;</p>
<p>The code above actually draws a gradient from <code>rgba(0,0,255,0.33)</code> to <code>rgba(0,0,255,0.33)</code> using a Microsoft-proprietary “extended” hex format that places the Alpha parameter first (instead of last) and in the range of 00-FF (instead of 0-1). The rest is a usual hex color, in that case #0000FF.</p>
<p><strong>Caution:</strong> The “gradients” that are created via the gradient filter are placed <strong>on top</strong> of any backgrounds currently in effect. So, if you want to have a background image as well, the result may not be what you expected. If you provide a solid color as a background, it will also not work as expected (no alpha transparency), since the gradients created are not exactly backgrounds, they are just layers <strong>on top</strong> of backgrounds.</p>
<h3 id="problems-with-the-filter-method" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#problems-with-the-filter-method">Problems with the filter method</a></h3>
<ul>
<li>Filters are bad for client-side performance.</li>
<li>Filters cause the text rendering to be aliased and especially when it’s bold and there is no background-color set it becomes completely unreadable. (the worst disadvantage if you ask me)</li>
<li>Filters only work with IE. What about Firefox 2- and Opera 9.6-?</li>
<li>Filters are lengthy (especially now that you have to include 2 different syntaxes) so they significantly increase the size of your CSS when used frequently.</li>
<li>You have to convert the red, green and blue values to hex to use that method.</li>
<li>To use a filter, the element has to <a href="http://haslayout.net/">have Layout</a>. This is usually done via zoom:1. More non-standard clutter in your CSS.</li>
<li>Doesn’t play along well with other workarounds, since it doesn’t modify the background of the element.</li>
</ul>
<p>So, personally, I only use that approach sparingly, in particular, only when “no/minimum external files” is a big requirement.</p>
<h3 id="a-bulletproof-solution" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#a-bulletproof-solution">A bulletproof solution</a></h3>
<p>My favored approach is to use rgba() for all RGBA-capable browsers and fallback pngs for the ones that don’t support RGBA. However, creating the pngs in Photoshop, or a similar program and then uploading them is too much of a fuss for me to bare (I get bored easily :P ). So, I created a small PHP script that:</p>
<ul>
<li>Creates a 1-pixel png image with the parameters passed for red, green, blue and alpha. No need to convert to hex.</li>
<li>Supports named colors, to speed up typing even more for colors that you use commonly in a site (it includes white and black by default, but you may easily add as many as you like).</li>
<li>Stores the generated images on the server, so that they don’t have to be created every time (generating images on the fly has quite an important performance impact).</li>
<li>Forces the images to be cached on the browser so that they don’t have to be generated every time (even though their size is very small, about 73 bytes).</li>
</ul>
<p>Here it is: <a href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/uploads/rgba.zip">rgba.php</a></p>
<p>You use it like this:</p>
<p>background: url(rgba.php?r=255&g=100&b=0&a=50) repeat;
background: rgba(255,100,0,0.5);</p>
<p>or, for named colors:</p>
<p>background: url(rgba.php?name=white&a=50) repeat;
background: rgba(255,255,255,0.5);</p>
<p>Browsers that are RGBA-aware will follow the second background declaration and will not even try to fetch the png. Browsers that are RGBA-incapable will ignore the second declaration, since they don’t understand it, and stick with the first one. <strong>Don’t change the order of the declarations: The png one goes first, the rgba() one goes second.</strong> If you put the png one second, it will always be applied, even if the browser <strong>does</strong> support rgba.</p>
<p>Before you use it, open it with an editor to specify the directory you want it to use to store the created pngs (the default is <code>'colors/'</code>) and add any color names you want to be able to easily address (the defaults are white and black). If the directory you specify does not exist or isn’t writeable you’ll get an error.</p>
<p><strong>Caution:</strong> You have to enter the alpha value in a scale of 0 to 100, and not from 0 to 1 as in the CSS. This is because you have to urlencode dots to transfer them via a URI and it would complicate things for anyone who used this.</p>
<p><strong>Edit:</strong> It seems that IE8 sometimes doesn’t cache the image produced. I should investigate this further.</p>
<p><strong>IMPORTANT: If your PHP version is below 5.1.2 perform <a href="http://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#comment-101">this change</a> in the PHP file or it won’t work.</strong></p>
<h3 id="why-not-data%3A%2F%2F-uris%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#why-not-data%3A%2F%2F-uris%3F">Why not data:// URIs?</a></h3>
<p>Of course, you could combine the IE gradient filter, rgba() and <code>data://</code> URIs for <a href="http://www.webdevelopedia.com/better_opacity.html">a cross-browser solution that does not depend on external files</a>. However, this approach has some disadvantages:</p>
<ul>
<li>All the <a href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#filter-issues">disadvantages of filters mentioned above</a>.</li>
<li>You can’t be spontaneous in your CSS and changes are difficult. Every time you want to use RGBA, you have to resort to some converter to create the png and it’s <code>data://</code> URI. Unless you are some kind of a cyborg with an embedded base64 encoder/decoder in your head :P</li>
<li>Larger filesize (you have to use 4-5 declarations (the rgba() one, the <code>data://</code> one, 2 filters, one for IE7- and one for IE8 and a <code>zoom:1;</code> to give the element “layout” so that filters can be applied) instead of 2, and the data:// URI has the same size as the png). Also, the <code>data://</code> URI can not be cached so every time you use it, you increase the filesize even more. Ok, you save an http request per use, but is it worth it?</li>
</ul>
<p>and some advantages:</p>
<ul>
<li>You will not see the site without a background for even a single millisecond. Since the png is embedded in the CSS, it’s loaded as soon as the CSS itself is loaded. If your site background is too dark and you rely on the RGBA background to make the content legible, you might want to consider this solution.</li>
<li>No external files, no extra http requests.</li>
<li>The filter method works in IE6- without the script for transparent PNGs.</li>
</ul>
<p>Choose the method that fits your needs better. :)</p>
<h3 id="rgba-is-not-only-for-backgrounds!" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#rgba-is-not-only-for-backgrounds!">RGBA is not only for backgrounds!</a></h3>
<p>It’s also for every CSS property that accepts color values. However, backgrounds in most cases are the easiest to workaround. As for borders, if you want solid ones, you can simulate them sometimes by wrapping a padded container with an RGBA background around your actual one and giving it as much padding as your desired border-width. For text color, sometimes you can fake that with opacity. However, these “solutions” are definitely incomplete, so you’d probably have to wait for full RGBA support and provide solid color fallbacks for those (unless someone comes up with an ingenious solution in <code><canvas></code>, it’s common these days :P ).</p>
Silent, automatic updates are the way to go2009-02-18T00:00:00Zhttps://lea.verou.me/?p=100<p>Recently, PPK stated that <a href="http://www.quirksmode.org/blog/archives/2009/02/state_of_the_br.html#link4">he hates Google Chrome’s automatic updates</a>. I disagree. In fact, I think that all browser vendors should enforce automatic updates as violently as Google Chrome does. There should be no option to disable them. For anybody.</p>
<h3 id="but-what-about-the-user%E2%80%99s-freedom-of-choice%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/silent-automatic-updates-are-the-way-to-go/#but-what-about-the-user%E2%80%99s-freedom-of-choice%3F">But what about the user’s freedom of choice?</a></h3>
<p>This might sound a bit facist at start, but imagine a world where all browsers would get automatically updated, without the possiblity of an opt-out. If you went online, you would be bound to have the very latest version, regardless of how computer (i)literate you were (Many — if not most — home users that don’t upgrade are like that because they think it’s too difficult for their computer expertise level). Sure, if you were a developer you wouldn’t be able to test a website in older browser versions. But why would you need to do so? If everybody had the latest browser version, you would only develop for the latest version and perhaps for the next one (via nightlies and betas, that could still be separate in that ideal world).</p>
<p>Imagine a world where your job wouldn’t have to involve tedious IE6 (and in a few weeks, no IE7 either), Firefox 2, Opera 9.5 and Safari 3.1- testing. A world where you would spend your work hours on more creative stuff, where you wouldn’t want to bang your head on the wall because you know you did nothing wrong but the ancient browser that you are currently testing in is just incompetent and YOU have to fix it’s sh*t. A world where the size of your Javascript code (and the JS libraries’ code) would be half its size and constantly decreasing as new browser versions come out. A world where you would only have 1 CSS file in most websites you develop. A world where you wouldn’t feel so bad because IE8 doesn’t support opacity, border-radius or SVG, because you would know that in 1-2 years everyone would have IE9 and it will probably support them. A world where designing a website would be as much fun as designing your personal blog.</p>
<p>Doesn’t such a world sound like a dream? Would it harm anyone? Users would browse a much lighter and beautiful web, with a more feature-rich and secure browser. Developers would work half as much to produce better results and they would enjoy their work more.</p>
<h3 id="what-about-corporate-intranets-and-abandoned-sites-that-won%E2%80%99t-keep-up%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/silent-automatic-updates-are-the-way-to-go/#what-about-corporate-intranets-and-abandoned-sites-that-won%E2%80%99t-keep-up%3F">What about corporate intranets and abandoned sites that won’t keep up?</a></h3>
<p>Oh come on, that isn’t a good enough reason to not make that dream come true! Companies and individuals could be allowed to have an older version of the browser installed <strong>as well</strong>. They still wouldn’t be able to opt out from the automatic upgrade, but they could apply somehow to have an older version of the browser in the same system as well. Similarly to what happens now with browser betas. People would use the older version to access corporate intranet applications and obsolete sites and the latest version to surf the web. I may be overly optimistic, but I think that if a user had both versions of a browser installed, (s)he would prefer the latest wherever (s)he can. Perhaps another step towards enforcing that would be if the OS prevented an older browser version from being set as the default browser, but I guess that would be too hard to do, especially if the browser in question is not the OS default one.</p>
<h3 id="other-people-who-agree-with-me" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/silent-automatic-updates-are-the-way-to-go/#other-people-who-agree-with-me">Other people who agree with me<a href="http://www.aaronboodman.com/2009/01/update-fail.html"></a></a></h3>
<ul>
<li><a href="http://www.aaronboodman.com/2009/01/update-fail.html">Aaron Boodman</a></li>
<li><a href="http://directwebremoting.org/blog/joe/2009/02/04/undoable_silent_autoupdate.html">Joe Walker</a></li>
</ul>
<p>What’s <strong>your</strong> opinion?</p>
20 things you should know when not using a JS library2009-02-22T00:00:00Zhttps://lea.verou.me/?p=119<p>You might just dislike JavaScript libraries and the trend around them, or the project you’re currently working on might be too small for a JavaScript library. In both cases, I understand, and after all, who am I to judge you? I don’t use a library myself either (at least not one that you could’ve heard about ;) ), even though I admire the ingenuity and code quality of some.</p>
<p>However, when you take such a brave decision, it’s up to you to take care of those problems that JavaScript libraries carefully hide from your way. A JavaScript library’s purpose isn’t only to provide shortcuts to tedious tasks and allow you to easily add cool animations and Ajax functionality as many people (even library users) seem to think. Of course these are things that they are bound to offer if they want to succeed, but not the only ones. JavaScript libraries also have to workaround browser differences and bugs and this is the toughest part, since they have to constantly keep up with browser releases and their respective bugs and judge which ones are common enough to deserve workaround and which ones are so rare that would bloat the library without being worth it. Sometimes I think that nowadays, how good of a JavaScript developer you are doesn’t really depend on how well you know the language, but rather on how many browser bugs you’ve heard/read/know/found out. :P</p>
<p>The purpose of this post is to let you know about the browser bugs and incompatibilities that you are most likely to face when deciding againist the use of a JavaScript library. Knowledge is power, and only if you know about them beforehand you can workaround them without spending countless debugging hours wondering “WHAT THE…”. And even if you do use a JavaScript library, you will learn to appreciate the hard work that has been put in it even more.</p>
<p>Some of the things mentioned below might seem elementary to many of you. However, I wanted this article to be fairly complete and contain as many common problems as possible, without making assumptions about the knowledge of my readers (as someone said, “assumption is the mother of all fuck-ups” :P ). After all, it does no harm if you read something that you already know, but it does if you remain ignorant about something you ought to know. I hope that even the most experienced among you, will find at least one thing they didn’t know very well or had misunderstood (unless I’m honoured to have library authors reading this blog, which in that case, you probably know all the facts mentioned below :P ) . If you think that something is missing from the list, feel free to suggest it in the comments, but have in mind that I conciously omitted many things because I didn’t consider them common enough.</p>
<h3 id="dom" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/#dom">DOM</a></h3>
<ol>
<li><code>getElementById('foo')</code> also returns elements with <code>name="foo"</code> in IE and older versions of Opera.</li>
<li><code>getElementsByTagName('*')</code> returns <strong>no</strong> elements in IE5.5 and also returns comment nodes in all versions of IE (In case you’re wondering: <code>DOCTYPE</code> declaration will count, Conditional comments will not).</li>
<li><code>getElementsByClassName()</code> in Opera (even Opera 10 Alpha) doesn’t match elements with 2 or more classes when the one you’re looking for is not the first but it’s also a substring of the first. Read the discussion between me and John Resig on the latter’s blog post mentioned below if this seems a bit unclear.</li>
<li><a href="http://www.quirksmode.org/dom/w3c_core.html#t71">There is no <code>element.children</code> collection in Firefox 3-</a>. You have to create it yourself by filtering the <code>childNodes</code> collection if it doesn’t exist.</li>
<li>If your code involves preformatted elements (for instance if you are making a syntax highlighter), beware when setting the <code>innerHTML</code> of those: <a href="http://www.quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html">IE won’t preserve line breaks (<code>\r\n</code> s) and whitespace</a>. You have to use <code>outerHTML</code>, which will actually replace the element so you should find a way to obtain a reference to the newly created one in case you still need to perform stuff on it.</li>
<li>To get the dimensions of the viewport, <a href="http://www.quirksmode.org/dom/w3c_cssom.html#t00">standard compliant browsers use <code>window.innerWidth</code> (and <code>innerHeight</code>)</a> whereas IE uses <code>document.documentElement.clientWidth</code> (and <code>clientHeight</code>).</li>
<li>To get the scroll offsets of the current page, <a href="http://www.quirksmode.org/dom/w3c_cssom.html#t02">standard compliant browsers use <code>window.pageXOffset</code> (and <code>pageYOffset</code>)</a> whereas IE uses <code>document.documentElement.scrollTop</code> (and <code>scrollLeft</code>).</li>
<li>To make matters worse, in both cases above, you need to use <code>document.body</code> instead of <code>document.documentElement</code> when in Quirks mode.</li>
</ol>
<p>John Resig (of the jQuery fame), recently <a href="http://ejohn.org/blog/the-dom-is-a-mess/">posted a great presentation</a>, which summarized some browser bugs related to DOM functions. A few of the bugs/inconsistencies mentioned above are derived from that presentation.</p>
<h3 id="events" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/#events">Events</a></h3>
<ol>
<li>When using IE’s <code>attachEvent()</code> the <code>this</code> reference inside the callback refers to the useless <code>window</code> object</li>
<li><code>eventObject.target</code> is <code>eventObject.srcElement</code> in IE</li>
<li><code>eventObject.stopPropagation()</code> is <code>eventObject.cancelBubble = true;</code> in IE</li>
<li><code>eventObject.preventDefault()</code> is <code>eventObject.returnValue = false;</code> in IE</li>
<li>There are many more event object incompatibilities for specific events (the ones above are for <strong>all</strong> events). Take a trip to <a href="http://www.quirksmode.org/dom/w3c_events.html">QuirksMode</a> for more information.</li>
<li>IE leaks horribly (especially IE6) in <a href="http://msdn.microsoft.com/en-us/library/bb250448.aspx">various cases</a>.</li>
<li>If you register the same event handler X times, IE fires it X times.</li>
<li>Determining when the DOM is ready is <strong>a complete mess</strong>. Firefox and Opera 9+ support the <code>DOMContentLoaded</code> event, Safari doesn’t but <a href="http://peter.michaux.ca/articles/the-window-onload-problem-still#webkitAndDocumentReadyState">you can check it’s <code>document.readyState</code> property</a> and <a href="http://peter.michaux.ca/articles/the-window-onload-problem-still#InternetExplorerAndDocumentReadyState">in IE <code>document.readyState</code> is unreliable</a> and you should either <a href="http://peter.michaux.ca/articles/the-window-onload-problem-still#InternetExplorerAndDefer">inject a deferred script</a>, either <a href="http://javascript.nwbox.com/IEContentLoaded/">poll the DOM untill there are no errors</a> or <a href="http://dean.edwards.name/weblog/2005/09/busted2/">use an external behavior file</a>. Of course you could always just put <a href="http://peter.michaux.ca/articles/the-window-onload-problem-still#bottomScript">a <code>script</code> tag at the bottom of the page, just before the <code>body</code> closing tag, which will fire all attached handlers</a> which is actually the best approach in terms of which way fires earliest (but not too early) according to my tests, but that hardly qualifies as unobtrusive…</li>
<li><strong>(edit, thanks Sobral!)</strong> The Event object is not passed as a parameter to the callback but resides in <code>window.event</code> in older versions of IE</li>
</ol>
<h3 id="type-detection" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/#type-detection">Type detection</a></h3>
<ol>
<li>
<p>The <code>typeof</code> operator is almost useless:</p>
<ul>
<li><code>typeof null == 'object'</code></li>
<li><code>typeof new String('foo') == 'object'</code></li>
<li><code>typeof [] == 'object'</code></li>
</ul>
<p><a href="http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/">Use Object.prototype.toString instead</a>.</p>
</li>
</ol>
<h3 id="css" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/#css">CSS</a></h3>
<ol>
<li>Although most CSS properties are converted to their JavaScript equivalents in a standard way (characters after dashes are Uppercase, others are lowercase, the dashes get removed), float is an exception: It’s converted to cssFloat in most browsers and styleFloat in IE. Check <a href="http://lea.verou.me/2009/02/check-if-a-css-property-is-supported/">which one exists</a> and use that.</li>
<li>Getting the current (computed) style of an element is another <strong>complete mess</strong>. IE uses <code>element.currentStyle[propertyJS]</code> whereas standard compliant browsers use <code>document.defaultView.getComputedStyle(element, null).getPropertyValue(propertyCSS)</code>. And as if this wasn’t enough, there are various problems associated with specific properties or browsers, like:
<ul>
<li>IE returns the cascaded values and not the computed ones (for instance, it might return <code>em</code>s for a property that was specified in <code>em</code>s, and not pixels). <a href="http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291">Dean Edwards has thought a very clever hack to workaround this</a> and didn’t even blog about it (it’s simply a comment in a blog post of Erik Arvidsson’s!).</li>
<li>Any hidden (via <code>display:none;</code>) element, yields a <code>width</code>/<code>height</code>/<code>top</code>/<code>right</code>/<code>bottom</code>/<code>left</code> value of zero.</li>
<li><code>auto</code> or <code>normal</code> might be returned for properties that are left at their defaults. For instance, IE does this with <code>width</code>/<code>height</code> for elements that don’t have dimensions explicitly set via CSS.</li>
<li>In most browsers, shorthands (like <code>border</code>) will yield a blank string. You’d have to use the most specific property (for instance, <code>border-left-width</code>).</li>
<li>Colors will be returned in different formats across browsers. For instance, IE uses <code>#RRGGBB</code> whereas Mozilla uses <code>rgb(red, green, blue)</code>.</li>
</ul>
</li>
</ol>
<h3 id="so%2C-what-now%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/#so%2C-what-now%3F">So, what now?</a></h3>
<p>Never, EVER use a browser detect to solve the problems mentioned above. They can <strong>all</strong> be solved with feature/object detection, simple one-time tests or defensive coding. I have done it myself (and so did most libraries nowadays I think) so I know it’s possible. I will not post all of these solutions to avoid bloating this post even more. You can ask me about particular ones in the comments, or read the uncompressed source code of any library that advertises itself as “not using browser detects”. JavaScript Libraries are a much more interesting read than literature anyway. :P</p>
<h3 id="are-the-facts-mentioned-above-actually-20%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/#are-the-facts-mentioned-above-actually-20%3F">Are the facts mentioned above actually 20?</a></h3>
<p>I’m not really sure to be honest, it depends on how you count them. I thought that if I put a nice round number in the title, it would be more catchy :P</p>
Quick & dirty way to run snippets of JavaScript anywhere2009-02-23T00:00:00Zhttps://lea.verou.me/?p=172<p>Ever wanted to run a snippet of JavaScript on a browser that doesn’t support a console in order to debug something? (for instance, IE6, Opera etc)</p>
<p>You probably know about <a href="http://getfirebug.com/lite.html">Firebug Lite</a>, but this either requires you to already have the bookmarklet, or include the script in the page. Although Firebug Lite is a great tool for more in depth debugging, it can be tedious for simple tasks (eg. <em>“What’s the value of that property?”</em>).</p>
<p>Fortunately, there is a simpler way. Do you remember the 2000 era and the <code>javascript:</code> URIs? Did you know that they also work from the address bar of <strong>any</strong> javascript-capable browser?</p>
<p>For instance, to find out the value of the global variable <code>foo</code>, you just type in the address bar <code>javascript:alert(foo)</code>. You can write any code you wish after the <code>javascript:</code> part, as long as you write it properly to fit in one line.</p>
<p>Of course these URIs are a no-no for websites, but they can be handy for simple debugging in browsers that don’t support a console. ;)</p>
"Appearances can be deceiving Mr. Anderson" - a.k.a. short code is not always fast code2009-02-25T00:00:00Zhttps://lea.verou.me/?p=182<p>I used to take pride in my short, bulletproof and elegant String and Number type checks:</p>
<pre><code class="language-js">// Check whether obj is a Number
obj + 0 === obj
// Check whether obj is a String
obj + '' === obj
</code></pre>
<p>I always thought that apart from being short and elegant, they <strong>should</strong> be faster.</p>
<p>However, some quick tests gave me a cold slap in the face and proved my assertion to be entirely false. When comparing the following 4 methods for string and number type checking:</p>
<ol>
<li>“My” method (mentioned above)</li>
<li>Object.prototype.toString method: <code>Object.prototype.toString.call(obj) === '[object String]'</code> or <code>Object.prototype.toString.call(obj) === '[object Number]'</code></li>
<li>Typeof method: <code>typeof obj === 'string'</code> or <code>typeof obj === 'number'</code></li>
<li>Contructor method: <code>obj.constructor === String</code> or <code>obj.constructor === Number</code></li>
</ol>
<p>It turned out that the Object.prototype.toString method was <strong>50%</strong> faster than my method, and both typeof and constructor methods were a whopping <strong>150%</strong> faster than my method! No wonder <a href="http://docs.jquery.com/JQuery_Core_Style_Guidelines">jQuery uses the typeof method for their String/Number tests</a>.</p>
<p>Now that I think about it, it does actually make sense - my method converts <code>obj</code> to a String or Number, then concatenates/adds it with another String/Number, then compares value and type. Too much stuff done there to be fast. But I guess I was too innocent and subconsciously thought that it wouldn’t be fair if elegant and short code wasn’t fast too.</p>
<p>Of course the overall time needed for any of these tests was neglible, but it’s a good example of how much appearances can be deceiving - even in programming! ;)</p>
<p>The moral: Never assume. Always test.</p>
<h3 id="so%2C-which-method-is-ideal-for-string%2Fnumber-checks%3F-(added-afterwards)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/02/appearances-can-be-deceiving-mr-anderson-aka-short-code-is-not-always-fast-code/#so%2C-which-method-is-ideal-for-string%2Fnumber-checks%3F-(added-afterwards)">So, which method is ideal for String/Number checks? (added afterwards)</a></h3>
<p>The typeof method and my method fail for non-primitive String/Number objects, as you can easily observe if you type in the console:</p>
<pre><code class="language-js">typeof new String('foo') // 'object'
typeof new Number(5) // 'object'
new String('foo') + '' === new String('foo') // false
</code></pre>
<p>This can easily be solved if you also check the type via instanceof (the decrease in speed is negligible):</p>
<pre><code class="language-js">foo = new String('foo');
typeof foo === 'string' || foo instanceof String
foo + '' === foo || foo instanceof String
</code></pre>
<p>Don’t use instanceof alone, since it fails for String and Number primitives. The instanceof method also fails for Strings and Numbers created in another window, since their constructor there is different. Same happens with the Constructor method mentioned above.</p>
<p>It seems that if you need a <strong>bulletproof</strong> check the only method you can use is the Object.prototype.toString method and luckily, it’s one of the fastest (not the fastest one though), so I guess we can safely elect it as the ideal method for String and Number checks (and not only for arrays, as it was first made popular for).</p>
<p>PS: For anyone wondering what the quote in the title reminds him/her, its from the <em>Matrix Revolutions</em> movie.</p>
Check whether the browser supports RGBA (and other CSS3 values)2009-03-01T00:00:00Zhttps://lea.verou.me/?p=203<p>When using CSS, we can just include both declarations, one using rgba, and one without it, as mentioned in my post on <a href="http://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/">cross-browser RGBA backgrounds</a>. When writing JavaScript however, it’s a waste of resources to do that (and requires more verbose code), since we can easily check whether the browser is RGBA-capable, almost as easily as we can check <a href="http://lea.verou.me/2009/02/check-if-a-css-property-is-supported/">whether it suppports a given property</a>. We can even follow the same technique to detect the support of other CSS3 values (for instance, <a href="http://www.css3.info/preview/multiple-backgrounds/">multiple backgrounds</a> support, <a href="http://www.css3.info/preview/hsla/">HSLA</a> support, etc).</p>
<p>The technique I’m going to present is based on the fact that when we assign a non-supported CSS value on any supported CSS property, the browser either throws an error AND ignores it (IE-style), or simply ignores it (Firefox-style). Concequently, to check whether RGBA is supported, the algorithm would be:</p>
<ol>
<li>Get the <code>color</code> (or <code>backgroundColor</code>, or <code>borderColor</code> or any property that is widely supported and accepts color values) value of the <code>style</code> object of any element that exists in the page for sure (for instance, the first script tag) and store it in a variable.</li>
<li>Assign an RGBA color to the <code>color</code> property of that element and catch any errors produced.</li>
<li>Assign to a variable whether the <code>color</code> of that element did change (boolean <code>true</code> or <code>false</code>).</li>
<li>Restore the previous color to the <code>color</code> property, so that our script doesn’t interfere with the designer’s decisions.</li>
<li>Return the stored result.</li>
</ol>
<p>and it would result in the following code:</p>
<pre><code class="language-js">function supportsRGBA()
{
var scriptElement = document.getElementsByTagName('script')[0];
var prevColor = scriptElement.style.color;
try {
scriptElement.style.color = 'rgba(1,5,13,0.44)';
} catch(e) {}
var result = scriptElement.style.color != prevColor;
scriptElement.style.color = prevColor;
return result;
}
</code></pre>
<h3 id="performance-improvements" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/check-whether-the-browser-supports-rgba-and-other-css3-values/#performance-improvements">Performance improvements</a></h3>
<p>The code above works, but it wastes resources for no reason. Every time the function is called, it tests RGBA support again, even though the result will never change. So, we need a way to cache the result, and return the cached result after the first time the function is called.</p>
<p>This can be achieved in many ways. My personal preference is to store the result as a property of the function called, named <code>'result'</code>:</p>
<pre><code class="language-js">function supportsRGBA()
{
if(!('result' in arguments.callee))
{
var scriptElement = document.getElementsByTagName('script')[0];
var prevColor = scriptElement.style.color;
try {
scriptElement.style.color = 'rgba(0, 0, 0, 0.5)';
} catch(e) {}
arguments.callee.result = scriptElement.style.color != prevColor;
scriptElement.style.color = prevColor;
}
return arguments.callee.result;
}
</code></pre>
<h3 id="making-it-bulletproof" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/check-whether-the-browser-supports-rgba-and-other-css3-values/#making-it-bulletproof">Making it bulletproof</a></h3>
<p>There is a rare case where the script element might <strong>already</strong> have <code>rgba(0,0,0,0.5)</code> set as it’s color value (don’t ask me why would someone want to do that :P ), in which case our function will return <code>false</code> even if the browser actually supports RGBA. To prevent this, you might want to check whether the <code>color</code> property is already set to <code>rgba(0,0,0,0.5)</code> and return <code>true</code> if it is (because if the browser doesn’t support rgba, it will be blank):</p>
<pre><code class="language-js">function supportsRGBA()
{
if(!('result' in arguments.callee))
{
var scriptElement = document.getElementsByTagName('script')[0];
var prevColor = scriptElement.style.color;
var testColor = 'rgba(0, 0, 0, 0.5)';
if(prevColor == testColor)
{
arguments.callee.result = true;
}
else
{
try {
scriptElement.style.color = testColor;
} catch(e) {}
arguments.callee.result = scriptElement.style.color != prevColor;
scriptElement.style.color = prevColor;
}
}
return arguments.callee.result;
}
</code></pre>
<p>Done!</p>
On native, single-input, multiple file uploads2009-03-07T00:00:00Zhttps://lea.verou.me/?p=209<p>If you are following the current news on web development, you probably heard that the new Safari 4 has a great feature: It natively allows the user to select multiple files via a single input control, if you specify a value for the attribute <code>multiple</code>:</p>
<pre><code class="language-html"><input type="file" multiple>
</code></pre>
<p>or, in XHTML:</p>
<pre><code class="language-html"><input type="file" multiple="multiple" />
</code></pre>
<p>You might not know that <a href="http://ajaxian.com/archives/input-typefile-multiple-now-in-a-real-browser#comment-271852">Opera supported multiple file uploads for a while now, based on the earlier Web Forms 2.0 standard</a> in a slightly different (and more flexible) format:</p>
<pre><code class="language-html"><input type="file" min="1" max="9999″ />
</code></pre>
<h3 id="can-we-use-those-currently%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/#can-we-use-those-currently%3F">Can we use those currently?</a></h3>
<p>Sure we can, but we should provide fallbacks for the other browsers. Using these features will put pressure on the other browser vendors to implement them as well and generally, native is always better.</p>
<h3 id="how-can-we-find-out-whether-the-browser-supports-them%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/#how-can-we-find-out-whether-the-browser-supports-them%3F">How can we find out whether the browser supports them?</a></h3>
<h4 id="opera" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/#opera">Opera</a></h4>
<p>Opera supports accessing those <code>min</code> and <code>max</code> properties as properties of the element. So, it’s quite trivial to check whether Opera-style multiple inputs are supported:</p>
<pre><code class="language-js">var supportsMin = (function(){
var fi = document.createElement('input');
fi.type = 'file';
return fi.min === '' && fi.max === '';
})();
</code></pre>
<h4 id="safari-4" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/#safari-4">Safari 4</a></h4>
<p>In Safari 4 the check would be equally simple, if it supported accessing the <code>multiple</code> attribute as a property. Then we could easily check whether it’s boolean and conclude that Safari-style multiple inputs are supported:</p>
<pre><code class="language-js">var supportsMultiple = (function(){
var fi = document.createElement('input');
fi.type = 'file';
// The second check is probably redundant but what if in the future an implementor
// decides to make the file inputs to handle multiple selections by default?
// Yeah, it's not likely, but it's not entirely impossible.
return fi.multiple === false || fi.multiple === true;
})();
</code></pre>
<p>However, that’s currently not the case. The good news are that <a href="https://bugs.webkit.org/show_bug.cgi?id=24444">I reported this as a bug today, and the Webkit team fixed it</a>, so it will be possible in the next Webkit nightly!</p>
<h4 id="combining-the-two" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/#combining-the-two">Combining the two</a></h4>
<p>You can easily combine these two together with the workaround you prefer:</p>
<pre><code class="language-js">// Create a file input that allows multiple file selection
var fi = document.createElement('input');
fi.type = 'file';
if(fi.multiple === false || fi.multiple === true) {
fi.multiple = true;
}
else if(fi.min === '' && fi.max === '') {
fi.min = 1;
fi.max = 9999;
}
else {
// Our preferred workaround here
}
</code></pre>
<h3 id="what-about-mozilla%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/#what-about-mozilla%3F">What about Mozilla?</a></h3>
<p>Ok, we all know that IE will probably take years to implement similar functionality. But usually, the Mozilla team implements new and exciting stuff quite fast.</p>
<p>As it turns out, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=63687">there is a relevant ticket sitting in their Bugzilla</a> for a while now. If you want them to implement it, vote for it so that it’s priority increases.</p>
<p>If they do implement it in the way suggested, the code posted above will work for that too, without any changes - The advantages of feature detection baby! ;)</p>
CMYK colors in CSS: Useful or useless?2009-03-09T00:00:00Zhttps://lea.verou.me/?p=214<p>As someone who dealed a bit with print design in the past, I consider CMYK colors the easiest color system for humen to understand and manipulate. It’s very similar to what we used as children, when mixing watercolors for our drawings. It makes perfect sense, more than HSL and definately more than RGB. I understand that most of us are so accustomed to using RGB that can’t realise that, but try to think for a moment: Which color system would make more sense to you if you had no idea and no experience at all with any of them?</p>
<p>Personally, even though I have lots more experience with RGB, given the fact that most of my work will be displayed on screen and not printed on paper, when I think of a color I want, I can instantly find out the percentages of Cyan, Magenta, Yellow and blacK needed to create it. I can’t do that with HSL or RGB, I’d have to play a little bit with the color picker’s sliders. I sometimes start by specifying a color in CMYK and then tweaking it via RGB or HSL to achieve the exact color I need (since the CMYK gamut is smaller than the RGB gamut) and I find that much faster than starting with RGB or HSL right away.</p>
<p>Also, when you don’t have a color picker, it’s much easier to create beautiful colors with CMYK than it is with RGB. For example the CMYK magenta (0% Cyan, 100% Magenta, 0% Yellow, 0% blacK) is a much better color than the RGB Magenta (255 Red, 0 Green, 100% Blue).</p>
<p>Given the above, I’ve always thought how much I wanted to be able to specify CMYK colors in my CSS. I agree that sometimes this would result in crippling myself, since as I said above the CMYK gamut is smaller, but it has other significant advantages that I think it would make it a useful option for some people. There are algorithms available for CMYK to RGB conversion, and the browser could use those to display the specified color on the screen. Then, if the user decided to print the page, The CMYK colors could be used as-is for the printer. Another advantage, as none of the current CSS color formats allow us to control that. People who don’t find the CMYK color system easier for them to understand, they could still use it for their print stylesheets.</p>
<p>Also, graphic designers who decided to switch to web design would find it much easier to specify color values in a format they are already comfortable with.</p>
<p>To sum it up, the advantages that I think this option would provide us are:</p>
<ol>
<li>A color system that’s easier for most people to understand and manipulate.</li>
<li>The colors you get when combining “easy” CMYK values (0%, 50%, 100%) are more beatuful than the ones you get with “easy” RGB values (0, 128, 255). Bored people and people without a taste in color selection would create more beatuful websites this way and our eyes wouldn’t hurt.</li>
<li>We would be able to specify how our colors will get printed, something that is not currently possible at all. Extremely useful for print stylesheets.</li>
<li>It would be easier for graphic designers to switch to web design.</li>
</ol>
<p>And the format is very easy to imagine:</p>
<pre><code>background-color: cmyk(0, 100, 50, 0);
</code></pre>
<p>or</p>
<pre><code>background-color: cmyk(0%, 100%, 50%, 0%);
</code></pre>
<p>or</p>
<pre><code>background-color: cmyk(0, 1, 0.5, 0);
</code></pre>
<p>So, what do you think? Useful or useless?</p>
<p><strong>Edit:</strong> As it turns out, I’m not crazy! <a href="http://www.w3.org/TR/css3-gcpm/#cmyk-colors">The W3 already considers this for CSS3</a> with the 3rd format (from 0 to 1)! However, no browser supports it yet, not even Webkit nightlies… :(</p>
<h3 id="translations" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cmyk-colors-in-css-useful-or-useless/#translations">Translations</a></h3>
<ul>
<li><a href="http://www.cssnolanche.com.br/cores-cmyk-em-css-uteis-ou-inuteis/">Portuguese</a></li>
</ul>
CSS3 colors, today (MediaCampAthens session)2009-03-15T00:00:00Zhttps://lea.verou.me/?p=227<p>Yesterday, I had a session at <a href="http://mediacamp.gr/">MediaCampAthens</a> (a BarCamp-style event), regarding CSS3 colors. If you’ve followed <a href="http://lea.verou.me/tag/colors/">my earlier posts tagged with “colors”</a>, my presentation was mostly a sum-up of these.</p>
<p>It was my first presentation ever, actually, the first time I talked to an audience for more than 1 minute :P . This caused some goofs:</p>
<ul>
<li>When introducing myself, I said completely different things than I intended to and ended up sounding like an arrogant moron :P</li>
<li>I tried not to look at the audience too much, in order to avoid sounding nervous, and this caused me to completely ignore 2 questions (as I found out afterwards)! How embarrasing!</li>
<li>At a certain point, I said “URL” instead of “domain” :P</li>
</ul>
<p>Also, I had prepared some screenshots (you’ll see them in the ppt) and the projector completely screwed them up, as it showed any dark color as black.</p>
<p>Apart from those, I think it went very well, I received lots of positive feedback about it and the audience was paying attention, so I guess they found it interesting (something that I didn’t expect :P ).</p>
<p>Here is the presentation:</p>
<p>Please note that Slideshare messed up slide #8 and the background seems semi-transparent grey instead of semi-transparent white.</p>
<p>By the way, I also thought afterwards that I had made a mistake: -ms-filter is not required if we combine the gradient filter with Data URIs, since IE8 supports Data URIs (for images at least). Oops, I hate making mistakes that I can’t correct.</p>
<p><a href="http://www.facebook.com/home.php#/album.php?aid=66598&id=796271191">Here are some photos from my session</a>. If I did it correctly, every facebook user can see them. If I messed things up, tell me :P</p>
Mockup viewer bookmarklet2009-03-18T00:00:00Zhttps://lea.verou.me/?p=241<p>I usually view mockups in a browser, so that the impression I get is as close as possible to reality (I learned this the hard way: A mockup that seemed great in the neutral and minimalistic environment of a picture viewer, ended up looking way too fancy when viewed in a browser, something that I realized after having worked for 6 months on the site). If you do the same, I’m sure you’ll feel my pain: Every time I do that, I have to carefully scroll down just as much as to hide the margin that the browser adds, and left just as much as to center the image. Not to mention the click required to enlarge the image to full-size.</p>
<p>Not any more! I was so fed up today, that I wrote a little bookmarklet that does this. It enlarges the image to full size, removes the margins and scrolls the page left so that the image is centered. It works on any recent browser I’ve tested, and I think it will probably work in most browsers that web designers use (hint: not old IEs) :P</p>
<p>Enjoy.</p>
<p><a href="javascript:(function(){%20document.body.style.margin%20=%200;%20var%20inner%20=%20window.innerWidth%20||%20document.body.clientWidth,%20img%20=%20document.getElementsByTagName('img')[0];%20img.removeAttribute('width');%20img.removeAttribute('height');%20document.body.scrollLeft%20=%20(img.offsetWidth%20-%20inner)/2;%20})();" class="call-to-action">Mockup viewer</a></p>
<p>JS code:</p>
<pre><code class="language-js">(function(){
document.body.style.margin = 0;
var inner = window.innerWidth || document.body.clientWidth, img = document.getElementsByTagName('img')\[0\];
img.removeAttribute('width');
img.removeAttribute('height');
document.body.scrollLeft = (img.offsetWidth - inner)/2;
})();
</code></pre>
<p>If only it could also write the XHTML & CSS for the site… :P</p>
Cross browser, imageless linear gradients2009-03-20T00:00:00Zhttps://lea.verou.me/?p=249<p>I have to write a color picker in the near future and I wanted it to have those little gradients on top of the sliders that show you the effect that a slider change will have on the selected color. Consequently, I needed to create imageless gradients, in order to easily change them. My very first thought was creating many div or span elements in order to show the gradient. I rejected it almost instantly, for ovbious reasons (*cough* performance *cough*). My second thought was SVG for the proper browsers, and gradient filters for IE. As it turned out, <a href="http://wiki.svg.org/Inline_SVG">inline SVG was too much of a hassle</a> and I didn’t want to use Data URIs. My final thought was canvas for the proper browsers and gradient filters for IE.</p>
<p>Since I consider such a script very entertaining, I didn’t google it at all, I started coding right away. Time to have fun! :D After finishing it though, I googled it just out of curiosity and didn’t like the other solutions much (either the solution itself, or the code), so I decided to post it in case it helps someone. I also made a little test page, so that you may test out how it works. :)</p>
<p>The script is a class for the creation of linear 2-color gradients in any browser. It’s used like this:</p>
<pre><code class="language-js">var g = new Gradient(200, 100, '#000000', '#ff1166', true);
document.body.appendChild(g.canvas);
</code></pre>
<p>You can create and manipulate the Gradient object at any point (during or after DOM parsing) but you have to insert the element somewhere in the DOM after the DOM has finished parsing (which is common sense).</p>
<p>All the parameters in the constructor are optional and can be manipulated later. Their order is <code>width, height, startColor, endColor, vertical</code>.</p>
<p>Some notes:</p>
<ul>
<li>Its object oriented and doesn’t throw any strict warnings</li>
<li>Tested in IE6, IE7, IE8, Firefox 3, Safari 4b and Opera 9.6. Probably works with older versions of Firefox, Opera and Safari as well (as long as they support <code><canvas></code>), I’m just not able to test in them currently.</li>
<li>All it’s methods return the object, so they can be chained.</li>
<li>You can modify it to support RGBA as well, but you’d have to use a different format for IE (extended hex) and a different one for the proper browsers. I didn’t need that and it would make the script unnecessarily complex, so I didn’t implement it.</li>
</ul>
<p>Limitations (<strong>all</strong> these limitations are enforced by IE’s gradient filter):</p>
<ul>
<li>Only does linear gradients</li>
<li>The gradient can be either vertical or horizontal. No other angles.</li>
<li>The only color format supported is #RRGGBB.</li>
</ul>
<h3 id="properties" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#properties">Properties</a></h3>
<h4 id="canvas-(htmlelement)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#canvas-(htmlelement)">canvas (HTMLElement)</a></h4>
<p>The HTML Element that is being used to render the gradient. Either a <code><canvas></code> or a <code><span></code>. You have to use it at least once, in order to insert the element in the DOM. I preferred not to do this automatically, since it would be too restrictive.</p>
<h4 id="startcolor-(string)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#startcolor-(string)">startColor (String)</a></h4>
<p>The current start color of the gradient.</p>
<h4 id="endcolor-(string)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#endcolor-(string)">endColor (String)</a></h4>
<p>The current end color of the gradient.</p>
<h4 id="vertical-(boolean)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#vertical-(boolean)">vertical (Boolean)</a></h4>
<p>True if the gradient is vertical, false if it’s horizontal.</p>
<h4 id="width-(number)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#width-(number)">width (Number)</a></h4>
<p>The width of the gradient in pixels</p>
<h4 id="height-(number)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#height-(number)">height (Number)</a></h4>
<p>The height of the gradient in pixels</p>
<h3 id="methods" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#methods">Methods</a></h3>
<h4 id="paint(startcolor%2C-endcolor%2C-vertical)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#paint(startcolor%2C-endcolor%2C-vertical)">paint(startColor, endColor, vertical)</a></h4>
<p>Used to change the colors and/or the orientation of the gradient. All parameters are optional.</p>
<h4 id="resize(width%2C-height)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#resize(width%2C-height)">resize(width, height)</a></h4>
<p>Used to change the size of the gradient. Both parameters are optional.</p>
<h4 id="flip()" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#flip()">flip()</a></h4>
<p>Reverses the gradient (swaps endColor with startColor)</p>
<h4 id="rotate()" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#rotate()">rotate()</a></h4>
<p>Rotates the gradient by 90 degrees clockwise (should I add CCW too?)</p>
<h3 id="download" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/#download">Download</a></h3>
<ul>
<li><a href="http://lea.verou.me/scripts/gradient/gradient.js">gradient.js (2.7 KB)</a></li>
<li><a href="http://lea.verou.me/scripts/gradient/gradient-min.js">gradient-min.js (1.4 KB)</a></li>
<li><a href="http://lea.verou.me/scripts/gradient/">Test page</a></li>
</ul>
<p>Hope you find it useful :)</p>
100% Cyan in CMYK is NOT rgb(0,255,255)!!2009-03-23T00:00:00Zhttps://lea.verou.me/?p=259<p>As I mentioned in an earlier post of mine, I have to create a color picker, so I’ve already started to write the code for the Color class it’s going to use. I need it to natively support RGB, HSL, Lab and CMYK. And the latter part is causing unexpected trouble.</p>
<p>It seems that there is the notion out there that conversion from <a href="http://en.wikipedia.org/wiki/CMYK_color_model">CMYK</a> to <a href="http://en.wikipedia.org/wiki/RGB">RGB</a> is easy. Newsflash: <strong>It’s not</strong>. As every graphic designer knows, the CMYK <a href="http://en.wikipedia.org/wiki/Color_gamut">color gamut</a> is smaller than the the RGB color gamut (even the sRGB color gamut). You can’t take a CMYK color and convert it to an out-of-CMYK-gamut RGB color! That’s nonsense! And it’s precisely what most conversion <a href="http://www.easyrgb.com/index.php?X=MATH&H=14#text14">algorithms</a> and <a href="http://www.colourlovers.com/copaso/ColorPaletteSoftware">color pickers</a> out there do! Even <a href="http://kuler.adobe.com/">Adobe Kuler</a>!!! Since yesterday, I’ve studied dozens of algorithms and color pickers that claim to do CMYK -> RGB conversion, and every single one of them is <strong>wrong</strong>.</p>
<p>You can test a color picker that claims to support CMYK, or a CMYK <–> RGB conversion algorithm in the following simple way: Test how it converts the color CMYK(100%, 0, 0, 0) to RGB. If the result is rgb(0,255,255) then the algorithm is crap. rgb(0, 255, 255) is neither the same color, nor is it even in the CMYK color gamut! So basically, these algorithms convert a CMYK color to an RGB color that is outside of the CMYK color gamut! <strong>A color that cannot be represented with CMYK is supposed to be the result of a CMYK->RGB conversion?</strong> This is madness!</p>
<p>So far the only CMYK -> RGB converter that I’ve seen and does it right, is the one used by Adobe CS products (Photoshop, Illustrator, etc). And that makes me wonder why Kuler does it the wrong way, since they have already figured the algorithm! It’s crazy!</p>
<p>What’s even more strange is that I can’t even find which sRGB colors are usually out of the CMYK color gamut, so that I can adjust the algorithm I use properly (even if it just clipped the color to the nearest in-gamut one, it would be an improvement). I’ve been searching since yesterady even for that with no luck. I hope I don’t end up using the wrong algorithm myself too…</p>
Extend Math.log to allow for bases != e2009-03-26T00:00:00Zhttps://lea.verou.me/?p=265<p>As Math.log currently stands, it’s a bit useless. It only calculates natural logarithms (base e). We can easily modify it however, to calculate logarithms of any base:</p>
<pre><code class="language-js">Math.log = (function() {
var log = Math.log;
return function(n, a) {
return log(n)/(a? log(a) : 1);
}
})();
</code></pre>
<p>We can now specify the base as a second parameter, or still use the default one (Math.E) if we don’t specify one, so older scripts won’t break or if we want a shortcut to the natural logarithm. ;)</p>
Advocacy of JavaScript2009-03-30T00:00:00Zhttps://lea.verou.me/?p=269<p>I frequently meet these “hardcore” developers that deep (or not so deep) inside them, tend to underestimate JavaScript developers and boast about their own superiority. I’m sure that if you spent an important percentage of your career working with JavaScript and are even barely social, you definitely know what I’m talking about. It’s those desktop application programmers or these back-end developers that tend to consider JavaScript a toy, and try to convince you to engage in “more serious stuff” (if they appreciate you even a little; if they don’t they just mock you endlessly and/or look down on you).</p>
<p>Funnily enough, when most of these people are required to write JavaScript for some reason, one of the following happens:</p>
<ol>
<li>They write 2000-style code, which is usually the reason that most of them underestimate JavaScript so much: They think that <strong>everybody</strong> codes in JavaScript like themselves.</li>
<li>They desperately look for “a good library” because “it’s not worth wasting my time to learn that stuff”.</li>
<li>They actually learn the darn language and the relevant browser quirks and change their attitude towards JavaScript developers.</li>
</ol>
<p><a href="http://javascript.crockford.com/javascript.html">Douglas Crockford did it much better than me</a>, but I would like to take my turn in arguing against their most frequent claims, if I may.</p>
<h3 id="%E2%80%9Cjavascripters-are-not-really-developers.%E2%80%9D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/advocacy-of-javascript/#%E2%80%9Cjavascripters-are-not-really-developers.%E2%80%9D">“Javascripters are not really developers.”</a></h3>
<p>Oh r’ly? Is it because JavaScript doesn’t follow what you’ve learned to expect from most languages? Well, newsflash: Assembly doesn’t either and every programming language is actually an abstraction to it. It’s in fact much harder to write the same thing in a language that lacks what we’ve learned to expect. Think about low level coding: Even the simplest tasks seem hard. At a smaller extent, it’s the same with JavaScript: Things that are easy to do in other languages, are a pain in JavaScript, even if we leave out the implementation differences across browsers. For instance, in Java there is a built-in class for most common tasks. JavaScript isn’t that rich, and it penalizes you for every external library you use, by forcing your users to download extra Kilobytes of code. JavaScript is probably the only modern language in which short code isn’t only elegance, but also a necessity.</p>
<p>Also, in other languages, you only have to deal with <strong>one</strong> implementation. Even when using Java to code for multiple operating systems, the differences are minor for most applications. With JavaScript, you are dealing with at least 5 implementations with many differences and bugs to circumvent. Writing a piece of code that works in one browser is not good enough, you have to make it work across <strong>all</strong> major browsers, in <strong>all</strong> their versions that still have significant market share. And yeah, this is most of the times just as dreadful as it sounds, if not more.</p>
<p>Of course, I’m not implying that everyone who wrote a script in JavaScript is a developer, just like everyone that wrote a Hello World application in C++ is not a programmer. JavaScript is notorious for being used mostly by amateurs for the following reasons:</p>
<ol>
<li>Most people that ever wrote a webpage needed something that could only be done with JavaScript. Most of these people weren’t developers and didn’t have any interest in programming.</li>
<li>Because of (1) there are many JavaScript tutorials and books around for accomplishing simple tasks, most of them being leftovers from the 2000 era and promote bad code practices. During that era, people didn’t care about nice code, usability, accessibility and cross-browser functionality. They just wanted to get the job done spending the least possible time and they only cared if it worked in Internet Explorer.</li>
<li>Most people just copy and paste stuff from the tutorials mentioned in (2), leading to duplicate functionality, bad code, bad usability, complete absence of accessibility and buggy results in browsers other than the target one. This caused JavaScript to be related to these vices although these things were actually caused by abusing the language.</li>
</ol>
<h3 id="%E2%80%9Cjavascript-is-a-toy%2C-not-a-real-programming-language%E2%80%9D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/advocacy-of-javascript/#%E2%80%9Cjavascript-is-a-toy%2C-not-a-real-programming-language%E2%80%9D">“Javascript is a toy, not a real programming language”</a></h3>
<p>It may have been a toy in the 2000 era where your mind is still stuck. Currently, browser vendors are constantly adding new features to it, in order to make it able to compete with a fully-fledged programming language and competent front-end developers have been pushing JavaScript to an extent that was unimaginable when it was first introduced. If you are not convinced, pay a visit to <a href="http://www.chromeexperiments.com/">Chrome Experiments</a> (as the name suggests, you are advised to use Google Chrome when viewing them).</p>
<p><strong>JavaScript is not a light version of Java, nor is it a light version of any programming language.</strong> It has a soul of it’s own, so stop comparing it to other languages and pointing out the areas where it lacks. Open your eyes instead to see the areas where it’s superior to all other languages you probably know (<a href="http://www.hunlock.com/blogs/Functional_Javascript">lambda</a> for instance).</p>
<h3 id="%E2%80%9Chow-can-i-respect-a-language-that-only-lives-inside-a-browser%3F%E2%80%9D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/advocacy-of-javascript/#%E2%80%9Chow-can-i-respect-a-language-that-only-lives-inside-a-browser%3F%E2%80%9D">“How can I respect a language that only lives inside a browser?”</a></h3>
<p>Newsflash: You are wrong, <strong>again</strong>. You can code in JavaScript <a href="http://en.wikipedia.org/wiki/Server-side_JavaScript">for the server</a>, <a href="http://www.phpied.com/make-your-javascript-a-windows-exe/">create Windows executable files (.exe)</a>, create plugins and extensions for a plethora of applications, and actually even Flash’s ActionScript is based on ECMAScript, a standard that was derived from and currently controls JavaScript implementations.</p>
<h3 id="disclaimers" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/03/advocacy-of-javascript/#disclaimers">Disclaimers</a></h3>
<p>Ah, these are always necessary in rants :)</p>
<ol>
<li><strong>I didn’t have any particular individual in mind when writing this post, so if you think it’s about you, get over it.</strong> My memory is too bad to do so anyway. ;)</li>
<li>I am not implying that JavaScript is the best programming language around. I actually don’t think there is such a language. My point was that JavaScript is not inferior to the others. That doesn’t mean I consider it superior either.</li>
<li>I don’t claim to be a programming guru (anyone who does so is usually ignorant anyway), nor do I claim to be always right. Feel free to argue, if you have thought a valid counterargument. :)</li>
</ol>
Help me: take the color survey2009-04-06T00:00:00Zhttps://lea.verou.me/?p=286<p>If you are a creative professional, or just passionate about colors, please take my survey:</p>
<p><a href="http://bit.ly/colorsurvey">http://bit.ly/colorsurvey</a></p>
<p>It will greatly help me to make a future project of our company more usable (some of its features at least) and it only takes a few minutes (it contains 10-19 questions, depending on your responses).</p>
<p>Any suggestions, corrections, questions etc are of course welcome.</p>
<p>Thanks a lot in advance to everyone that takes the survey! :D</p>
<p>Of course, when it ends and I find the time to analyze the results, I’ll post them here for anyone interested. (Hint: That means that if you are interested in the results, you can promote the survey yourself as well, since more responses = more accurate results)</p>
Better usability in 5 minutes2009-04-10T00:00:00Zhttps://lea.verou.me/?p=296<p>In this post I’m going to share some tips to increase a site’s usability that are very quick to implement. Not all of them are cross-browser, but they are the icing on the cake anyway, nobody would mind without them.</p>
<h3 id="1.-make-buttons-and-button-like-links-appear-pressed" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#1.-make-buttons-and-button-like-links-appear-pressed">1. Make buttons and button-like links appear pressed</a></h3>
<p>This is a personal favorite. When you use CSS to style a button, or when you use an image (either as a background image or in the <img /> tag) to depict a fancy button, it will remain the same when being pressed in some or all browsers (depending on the case). You can use this easy trick to let the user know that he actually clicked something that is, indeed, clickable:</p>
<p>.mybutton:active {
position:relative;
top: 1px;
left: 1px;
}</p>
<p>which actually moves the button 1 pixel to the right and 1 pixel to the bottom when it’s being clicked. Try it, it’s actually quite convincing.</p>
<p>Other, equally quick options are: making the border inset, giving to the text a text-indent of 1px, reversing a gradient background (if you already use the reversed version somewhere else in the site, it is quick since you don’t have to use an image editor just for that), or a combination of them.</p>
<h3 id="2.-smooth-transitions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#2.-smooth-transitions">2. Smooth transitions</a></h3>
<p>This is a webkit-only tip, but as I said, it’s just the icing on the cake, so who cares? If a smooth transition is crucial to your design, by all means, write a script for that or use a library. If you were planning to go the CSS-only way anyway, this will significantly increase the user experience for webkit users.</p>
<p>Let’s suppose that the links in your page are normally blue, and red on hover. To make the transition from blue to red smooth for webkit users, only 2 lines are needed in the CSS:</p>
<p>a {
color:blue;
<strong>transition-property: color;
transition-duration: 1s;</strong>
}</p>
<p>a:hover {
color:red;
}</p>
<p>The first one (<code>transition-property</code>) tells the browser which CSS property to smoothly transition and the second one (<code>transition-duration</code>) how long you want the whole effect to last. It’s important to place those in the normal CSS rule and not the one with the :hover pseudoclass, because otherwise there will be no transition when the user mouses out of the element. Please note that you currently need to also include browser prefixes for these properties or just use <a href="http://leaverou.github.com/prefixfree">-prefix-free</a>.</p>
<h3 id="3.-add-dingbats-to-buttons-that-depict-their-functionality" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#3.-add-dingbats-to-buttons-that-depict-their-functionality">3. Add dingbats to buttons that depict their functionality</a></h3>
<p>We all know that most browsers don’t like dingbat-only fonts. However, there are some dingbats that are available in most web-safe unicode fonts. For instance, review the following examples:</p>
<h4 id="without-dingbats%3A" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#without-dingbats%3A">Without dingbats:</a></h4>
<p>Next Previous Done Favorite</p>
<h4 id="with-dingbats%3A" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#with-dingbats%3A">With dingbats:</a></h4>
<p>Next → ← Previous ✔ Done ♥ Favorite</p>
<p>There are named html entities for some of them, others have to be used by their hex unicode index like ꯍ (you have to test the last ones a lot, since not all are web-safe enough).</p>
<p>You can find many such dingbats with their unicode hex codes in <a href="http://www.copypastecharacter.com/" title="Linkification: http://www.copypastecharacter.com/">http://www.copypastecharacter.com/</a> and <a href="http://www.alanwood.net/unicode/dingbats.html">http://www.alanwood.net/unicode/dingbats.html</a>.</p>
<p>Of course, if you have the time, by all means, use normal icons. If you don’t however, I find symbols to be a handy alternative. Sometimes I also use them as icon placeholders in work in progress until I find the time to design real icons.</p>
<h3 id="4.-zebra-rows" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#4.-zebra-rows">4. Zebra rows</a></h3>
<p>This won’t work on IE and Firefox 3. You can increase readability of tables and some types of lists by slightly alternating the background color of the rows. You’ve probably seen this effect numerous times and it’s usually done via JavaScript or the server side code that generates the table. You can quickly do it with plain CSS3 however, if you don’t mind it not working in IE and older browser versions or don’t have the time for a complete cross-browser solution:</p>
<p>table.stats tr {
background:white;
}</p>
<p>table.stats tr:nth-child(odd) {
background:#f4f4f4;
}</p>
<h3 id="5.-highlight-the-current-target" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#5.-highlight-the-current-target">5. Highlight the current target</a></h3>
<p>This won’t work in IE and older browser versions. If a particular page has lots of content, navigable by anchors (for example a FAQ page), you can use the CSS3 :target pseudo-class to let the user know where they landed:</p>
<p>h3:target {
background:#FFFBCC;
}</p>
<p>The h3 will only get a #FFFBCC background when it’s actually the landing point for the user. For example, if it has the id “foo”, it will get an #FFFBCC background when the user navigates to #foo.</p>
<h3 id="that%E2%80%99s-all-folks" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/better-usability-in-5-minutes/#that%E2%80%99s-all-folks">That’s all folks</a></h3>
<p>Did it actually take more than 5 minutes? ;)</p>
Java pretty dates2009-04-21T00:00:00Zhttps://lea.verou.me/?p=312<p>First of all, sorry for not posting as frequently as before. I’m feverishly working on a new project with a really tight deadline and I don’t have as much time as I previously did.</p>
<p>For reasons that are irrelevant to this post, I have to write lots of Java code. So, sorry if I disappoint my fellow readers, but this post isn’t about JavaScript or CSS, it’s about Java. I wanted to display “pretty dates” (a bit like Twitter’s, for example <em>“yesterday”</em>, <em>“5 minutes ago”</em>, <em>“last year”</em> and so on) in a few places and I couldn’t find a Java implementation, so I decided to code my own.</p>
<p>For anyone that might need it, here it is:</p>
<pre><code class="language-clike">import java.util.Date;
/**
* Class for human-readable, pretty date formatting
* @author Lea Verou
*/
public class PrettyDate
{
private Date date;
public PrettyDate() {
this(new Date());
}
public PrettyDate(Date date) {
this.date = date;
}
public String toString() {
long current = (new Date()).getTime(),
timestamp = date.getTime(),
diff = (current - timestamp)/1000;
int amount = 0;
String what = "";
/**
* Second counts
* 3600: hour
* 86400: day
* 604800: week
* 2592000: month
* 31536000: year
*/
if(diff > 31536000) {
amount = (int)(diff/31536000);
what = "year";
}
else if(diff > 2592000) {
amount = (int)(diff/2592000);
what = "month";
}
else if(diff > 604800) {
amount = (int)(diff/604800);
what = "week";
}
else if(diff > 86400) {
amount = (int)(diff/86400);
what = "day";
}
else if(diff > 3600) {
amount = (int)(diff/3600);
what = "hour";
}
else if(diff > 60) {
amount = (int)(diff/60);
what = "minute";
}
else {
amount = (int)diff;
what = "second";
if(amount < 6) {
return "Just now";
}
}
if(amount == 1) {
if(what.equals("day")) {
return "Yesterday";
}
else if(what.equals("week") || what.equals("month") || what.equals("year")) {
return "Last " + what;
}
}
else {
what += "s";
}
return amount + " " + what + " ago";
}
}
</code></pre>
<p>Hope someone finds it useful. :)</p>
Cross-browser imageless linear gradients v22009-04-24T00:00:00Zhttps://lea.verou.me/?p=316<p>A while ago, I posted <a href="http://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/" title="Cross-browser imageless linear gradients v1">a script of mine for creating 2-color cross-browser imageless linear gradients</a>. As I stated there, I needed them for a color picker I have to create. And even though 2-color gradients are sufficient for most components, in most color spaces, I had forgotten an important one: <strong>Hue</strong>. You can’t represent Hue with a 2-color gradient! So, I had to revise the script, and make it able to produce linear gradients of more than 2 colors. Furthermore, I needed to be able to specify a fully transparent color as one of the gradient colors, in order to create the photoshop-like 2d plane used by the picker (and no, a static image background like the one used in most JS color pickers wouldn’t suffice, for reasons irrelevant with this post). I hereby present you <em>Cross-browser, imageless, linear gradients <strong>v2</strong></em>!</p>
<p>The API has stayed just the same, with the following differences:</p>
<ul>
<li>
<p>You may specify the keyword “transparent” instead of a #RRGGBB color (that was such a pain to implement btw!).</p>
</li>
<li>
<p>When creating a Gradient object, color strings are now defined in an array. Example:</p>
<p>var g = new Gradient(200, 100, [‘#000000’, ‘#ff1166’, ‘#23ff46’], true);</p>
</li>
<li>
<p>When calling <code>g.paint()</code> it now takes <strong>2</strong> arguments instead of 3: The new color array (or null if you don’t want that to change) and the direction (true for vertical, false for horizontal). For example:</p>
<p>g.paint([‘#000000’, ‘#ff1166’, ‘#23ff46’], true);</p>
</li>
<li>
<p>2 new methods have been added: <code>g.setColorAt(index, color)</code> and <code>g.direction(newDirection)</code>. The first allows you to set a particular gradient color (index starting from 0) and the second to alter or toggle the direction (if you specify a direction parameter, you set the direction, if you call it with no parameters, it toggles from horizontal to vertical).</p>
</li>
<li>
<p>The fields <code>g.startColor</code> and <code>g.endColor</code> have been replaced by the array <code>g.colors</code>.</p>
</li>
</ul>
<p><strong>Update</strong>: <strong>v2.0.1</strong> Fixed a small bug with the ‘transparent’ keyword that affected multi-color gradients in browsers != IE when the transparent color wasn’t first or last.</p>
<p>Enjoy:</p>
<p><a href="http://lea.verou.me/scripts/gradient2/gradient.js">gradient.js (5.1 KB)</a></p>
<p><a href="http://lea.verou.me/scripts/gradient2/gradient-min.js">gradient-min.js (2.7 KB)</a></p>
<p><a href="http://lea.verou.me/scripts/gradient2/">Test page</a></p>
Creating the perfect slider2009-04-24T00:00:00Zhttps://lea.verou.me/?p=321<p>I’ve previously discussed many times the color picker I have to create, and blogged about my findings on the way. An essential component of most color pickers is a slider control.</p>
<p>I won’t go through much techincal details or JavaScript code in this article (after all the usability guidelines presented don’t only apply to JavaScript applications, and this is why I used Adobe Kuler as a good or bad example for some of them), it’s been done numerous times before and I prefer being a bit original than duplicating web content. You can google it and various implementations will come up if you need a starting point.</p>
<p>Some might argue that I suffer from <a href="http://en.wikipedia.org/wiki/Not_Invented_Here">NIH syndrome</a>, but I prefer to code things my way when I think I can do something even <strong>a bit</strong> better. After all, if nobody ever tries to reinvent the wheel, the wheel stands no chances of improvement. In this case, I wanted to build the most usable slider ever (at least for color picking uses), or -from an arguably more conservative point of view- something significantly more usable than the rest (if you think about it, the two statements are equivalent, the first one just <em>sounds</em> more arrogant :P ).</p>
<p>I started by thinking about the way I personally use sliders and other form controls, and what bothers me most in the process. Then I combined that with the previously-done accessibility guidelines and the best slider implementations I’ve encountered (from a usability perspective), and here is what I came up with.</p>
<h3 id="requirements-for-the-perfect-slider-control" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/04/creating-the-perfect-slider/#requirements-for-the-perfect-slider-control">Requirements for the perfect slider control</a></h3>
<ol>
<li>It should be <a href="http://www.w3.org/WAI/intro/aria">ARIA</a>-compatible, so that disabled users can easily utilize it.</li>
<li>It should be focusable, so that you can Tab to it.</li>
<li>Of course the thumb should be draggable (why would you call it a slider otherwise anyway?)</li>
<li>Of course the slider should be labeled so that the user knows what to use it for.</li>
<li>Normal, hover and focus states should be different (at least in browsers supporting the :hover and :focus pseudo-classes)</li>
<li>You should be able to click somewhere in the rail and have the thumb <strong>instantly move there</strong>. Many slider implementations use animations for that, and even though I admit it raises the <em>wow</em> factor, I don’t think it’s good for usability. <strong>When I choose something, I want it to be instantly selected</strong>, I don’t want to wait for the pretty animation to finish, even if it’s short. Other implementations don’t move the slider to the point of the rail that you clicked, but just a bit <em>towards</em> it. I find that very annoying. <strong>I clicked there because I want the slider to go there, not <em>towards</em> there!</strong> If I wanted to increment/decrement it a bit, I’d use other methods (read below).</li>
<li>It should be keyboard-navigable. I think the ideal key-mappings are:
<ul>
<li><strong>Left and right arrow keys</strong> for small adjustments</li>
<li><strong>Page up/down</strong> and <strong>Ctrl + left and right arrow keys</strong> for big adjustments.</li>
<li><strong>Esc</strong> to focus out (blur)</li>
<li><strong>Home</strong> and <strong>End</strong> to navigate to the minimum and maximum respectively</li>
</ul>
</li>
<li>It should respond to the <strong>mousewheel</strong> (and this is where all current implementations I’ve tested fail misreably) when focused. Small adjustments for normal mousewheel movement, big adjustments if the <strong>Ctrl</strong> key is pressed as well. The pitfall to that is that you can’t cancel the default action (zoom in/out) in Safari. Why the Ctrl key and not Alt or Shift? Because we are accustomed to using the Ctrl key as a modifier. Alt and Shift are used more rarely. Especially designers (and for most color pickers they are a significant part of the target audience) are used in using the Ctrl key together with the mousewheel, since that’s a popular way for zooming or scrolling in most Adobe CS applications. Another important consideration when designing a mousewheel-aware slider, is to bind the event to the document element once the slider thumb is focused and unbind it when the slider thumb is blurred. Why? Because in most such cases, we don’t like to have to keep out mouse pointer on the slider to adjust it with the mousewheel. <strong>It being focused should suffice for letting the app know that this is what we want to adjust.</strong></li>
<li>The exact numerical choice of the user should be visible, not only in an indicator that is positioned in a static place, but also <strong>above the slider thumb and have it move as the slider thumb moves</strong>. <strong>I don’t want to have to look at two different places to see what I have selected!</strong> (the slider thumb and the indicator) Why above the slider thumb? Because if it’s below, the mouse pointer is likely to hide it. This movable indicator should be hidden once the user focuses out (as long as we provide another one that is positioned statically). <a href="http://kuler.adobe.com/#create/fromacolor">Adobe Kuler</a> does this fairly well, although it suffers from a few issues: When you click on the slider rail, the indicator doesn’t show up.</li>
<li>The user should be able to click at some point in the rail and start dragging right away, <strong>without lifting their mouse button in the meantime</strong>. Even though this sounds common-sense, I’ve seen <strong>many</strong> implementations that fail at it (including Kuler’s).</li>
</ol>
<p>So, that’s it! What do you think? Could you come up with anything else to add?</p>
9 reasons why I prefer MySQL to MS SQL Server2009-05-16T00:00:00Zhttps://lea.verou.me/?p=334<p>In the past, I used MySQL for any of my DBMS needs. It wasn’t really an informed decision based on solid facts, actually I had never really given it any thought. It was what most developers used, it was what vBulletin used (one of the main projects of my company is based on vBulletin), it was what most hosts had pre-installed, in other words, it was the popular choice and I went with the crowd.</p>
<p>Unlike most decisions taken that way, this one turned out to be correct (so far at least). In the university where I study (yeah, I do that too occasionally :P ), there is a great and extremely useful class on Database Systems offered in my semester. The only drawback is that it’s done on MS SQL Server. Consequently, I had to work with it quite a lot, and my conclusion was that MySQL is far superior (mostly syntax-wise as I don’t have the deep knowledge required to judge them fairly for other things, so don’t expect a deep analysis about performance or security - as far as I’m concerned, they are equally good at those). Here are a few reasons:</p>
<ol>
<li>No ENUM datatype. Yeah, of course I can define a column with a char/varchar type and add a constraint to only allow for particular strings, but this kinda defeats the purpose of <a href="http://dev.mysql.com/doc/refman/5.0/en/storage-requirements.html">memory saving that the ENUM datatype in MySQL offers</a>.</li>
<li>No INSERT IGNORE. Instead you have to go through hell to simulate that in MS SQL Server.</li>
<li>I hate it that I can’t use “USING(columnlabel)” in a JOIN query and I have to use “ON(table1.columnlabel = table2.colmnlabel)” all the time. Yeah, I know that the first one isn’t standard, but it’s shorter, cleaner, more elegant, and …you can still use “ON(…)” if you don’t like it. Having more options is never bad, is it?</li>
<li>With MySQL you may insert multiple rows at once elegantly (“INSERT INTO tablename (…), (…), …”), without using the “INSERT INTO tablename SELECT (…) UNION ALL SELECT (…) UNION ALL …” hack. Moreover, the elegant MySQL way also <a href="http://troels.arvin.dk/db/rdbms/#insert-multiple">happens to be the standard</a>, a standard that SQL Server doesn’t follow.</li>
<li>Triggers can only run per statement, and not per row. This isn’t really important, since for most cases, it’s more efficient to define a per statement trigger anyway, but it doesn’t do any harm to have an extra option, does it?</li>
<li>Paging is dead-easy on MySQL: SELECT * FROM foo LIMIT 10,20 . With MS SQL Server you have to <a href="http://www.sqlteam.com/article/server-side-paging-using-sql-server-2005">jump</a> <a href="http://www.asp101.com/articles/gal/effectivepaging/default.asp">through</a> <a href="http://sqltips.wordpress.com/2007/08/10/optimized-solution-of-paging-by-using-count-over-functionality/">hoops</a> to do the same thing, especially if your query is not trivial.</li>
<li>In MySQL, when you want to convert an integer to a hex string, you just call HEX(). In SQL Server you have to call an undocumented function and do some string manipulation to do the exact same thing.</li>
<li>MySQL runs on every platform, whereas with MS SQL Server you’re stuck with Windows.</li>
<li>Last but not least, MySQL is free (and when it’s not free, it’s at least cheap) and opensource :-)</li>
</ol>
Tip: Multi-step form handling2009-06-16T00:00:00Zhttps://lea.verou.me/?p=346<p>First of all, sorry for my long absence! I haven’t abandoned this blog, I was just really, really busy. I’m still busy, and this probably won’t change soon. However, I will still blog when I get too fed up with work or studying (this is one of these moments…). Now, let’s get to the meat.</p>
<h3 id="the-situation" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/06/tip-multi-step-form-handling/#the-situation">The situation</a></h3>
<p>In most web applications, even the simplest ones, the need for form handling will arise. There will be forms that need to be submitted, checked, processed or returned to the user informing them about any errors. A good empirical rule I try to follow is “Try not to produce URLs that don’t have a meaning if accessed directly”. It sounds simple and common-sense, doesn’t it? However, as Francois Voltaire said, “common sense is not so common”. I’ve seen variations of the following scenario several times, in several websites or even commercial web application software:</p>
<p>Lets assume we have a two step process, like a registration form with an arguably¹ bad usability. The hypothetical script is called register.php (PHP is just an example here, the exact language doesn’t matter, it could be register.jsp or anything else). The user fills in the information required for the first step, and if they get it right, they advance to something like register.php?step=2 to complete the rest of the information. They fill in their information there as well, and submit the form. Everything is fine.</p>
<h3 id="or-is-it%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/06/tip-multi-step-form-handling/#or-is-it%3F">Or is it?</a></h3>
<p>What we have done this way is that we have effectively created a completely useless URL. If someone tries to access register.php?step=2 directly (via their history for instance), we don’t have the POST data from the first step, so we either have to redirect them to the first step or, even worse, assume they are actually coming from the first step and present it to them full of errors telling them they got everything wrong. In both cases we have duplicate content, and in the second one, usability suffers a great deal.</p>
<p>So, the right way is to pass step=2 via POST as well. This way, the URL stays as it was (register.php) and we avoid all the problems mentioned above. So, we end up doing something like this:</p>
<pre><code class="language-html">... form fields here ...
<input type="hidden" name="step" value="2" />
<input type="submit" value="Create my account" />
</code></pre>
<h3 id="now-we%E2%80%99re-done.-or-not%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/06/tip-multi-step-form-handling/#now-we%E2%80%99re-done.-or-not%3F">Now we’re done. Or not?</a></h3>
<p>This works fine. However, there’s still room for improvement. We could get rid of the extra input element by utilizing the submit button. Yeah, it’s a form element too, even though we often overlook that and just focus on styling it. If we give it a name, it will get sent along with the other form fields. So instead of the html above, we can do that:</p>
<pre><code class="language-html">... form fields here ...
<input type="submit" name="step" value="2" />
</code></pre>
<h3 id="but-wait!-what-the-f*ck-is-that-%3F%3F%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/06/tip-multi-step-form-handling/#but-wait!-what-the-f*ck-is-that-%3F%3F%3F">But wait! What the f*ck is that ???</a></h3>
<p>Now usability suffers! Instead of our nice “Create my account” button, the user now sees a cryptic “2”. Who cares if it works or if it requires less code, if nobody understands how to register, right? Luckily for us, we don’t <strong>have</strong> to use the <code><input /></code> tag to create submit buttons. A better (in terms of styling, semantics, markup clarity etc), albeit less known, alternative exists: The <code><button /></code> tag. When using the <code><button /></code> tag, the label of the button is derived from the markup within the start and end tags (yeah, we can also have html elements in there, not only text nodes, in case you’re wondering), not from the value attribute. So, we can set it’s name and value attributes to whatever we want, and the user won’t notice a thing:</p>
<pre><code class="language-html">... form fields here ...
<button type="submit" name="step" value="2">Create my account</button>
</code></pre>
<p>It’s really simple, although not done often. I guess it’s one of these “OMG how come I’ve never thought about this??” kind of things. :P</p>
<p>¹ I firmly believe we should eliminate the number of steps required in any procedure and especially in registration forms that users are bored to fill in anyway. However, there’s an exception to that: If the form <strong>has</strong> to be big for some reason, breaking it into steps actually makes it <strong>more</strong> usable, since the user is not overwhelmed with all these fields. Another situation when this approach is favorable is when the second step is determined according to the data from the first, although thanks to JavaScript and Ajax, this is becoming obsolete nowadays.</p>
On password masking and usability2009-06-28T00:00:00Zhttps://lea.verou.me/?p=351<p>I just read <a href="http://www.useit.com/alertbox/passwords.html">Jakob Nielsen’s recent post in which he urged web designers/developers to stop password masking</a> due to it’s inherent usability issues. I found it an interesting read. Hey, at last, someone dared to talk about the elephant in the room!</p>
<p>In most cases password masking is indeed useless, but still, there are several cases where you need that kind of protection. He also points that out, suggesting a checkbox to enable the user to mask their entered password if they wish to do so. He also suggests that checkbox being enabled by default on sites that require high security.</p>
<p>I think the checkbox idea is really good, as long as it works in the opposite way: Password masking should <strong>always</strong> be the default and you should <strong>check</strong> the checkbox to <strong>show</strong> the characters you typed. This is in line with what Windows (Vista or newer) users are already accustomed to anyway:</p>
<p><img src="https://lea.verou.me/2009/06/on-password-masking-and-usability/images/vistawirelesspasscode.png" alt="Enter passphrase" title="Enter passphrase" /></p>
<p>This can (and should) be done with JavaScript alone: if the user has it turned off, no problem, just a regular old password field. Of course the checkbox should also be dynamically added, to prevent users with disabled JS from viewing a checkbox that does nothing at all.</p>
<p>This seems easy at first, even without a library (although, in this particular case, a library would greatly reduce the amount of code required, so much that I’m tempted to include a jQuery version as well):</p>
<pre><code class="language-js">window.onload = function() {
var passwords = document.getElementsByTagName('input');
for(var i=0; i<passwords.length; i++) {
if(passwords\[i\].type == 'password') {
var password = passwords\[i\];
var showCharsCheckbox = document.createElement('input');
showCharsCheckbox.type = 'checkbox';
showCharsCheckbox.onclick = (function(input) {
return function() {
input.type = this.checked? 'text' : 'password';
};
})(password);
var showCharsLabel = document.createElement('label');
showCharsLabel.appendChild(showCharsCheckbox);
showCharsLabel.appendChild(document.createTextNode('Show characters'));
// If the password field is inside a <label> element, we don't want to insert our label in there as well!
var previousSibling = /label/i.test(password.parentNode.nodeName)? password.parentNode : password;
// Check whether it's the last child of it's parent
if(previousSibling.nextSibling) {
previousSibling.parentNode.insertBefore(showCharsLabel, previousSibling.nextSibling);
}
else {
previousSibling.parentNode.appendChild(showCharsLabel);
}
}
}
}
</code></pre>
<p>However, nothing is ever simple, when you also need to support our <em>beloved</em> Internet Explorer. Most moderately experienced JavaScript developers have probably already understood what I’m talking about: The all time classic IE bug (still present in IE8…) in regards to setting an <input /> element’s type attribute. You can only set it once, for elements that are not already in the DOM. After that, it becomes read-only, and any attempt to set it results in a “The command is not supported” error. And when I say “any” attempt I mean it:</p>
<ul>
<li><code>element.setAttribute()</code></li>
<li><code>element.type</code></li>
<li><code>element.setAttributeNode()</code></li>
<li><code>element.removeAttribute()</code> and then <code>element.setAttribute()</code></li>
<li><code>element.cloneNode()</code>, then one of the above, then replacing the node with the clone</li>
</ul>
<p><strong>everything</strong> fails miserably.</p>
<p>I’ve encountered this problem several times in the past as well, but I could always think of an alternative way to do what I wanted without having to work around it. In this case, I don’t think there is one. So we’re left with two possible scenarios:</p>
<ul>
<li>
<p>Perform an easy test in the beginning to see whether this bug exists and proceed only if the browser isn’t naughty. This could be done with the following:</p>
<pre><code class="language-js">var supportsChangingTypeAttribute = (function() {
var input = document.createElement('input');
try {
input.type = 'password';
input.type = 'text';
} catch(e) {
return false;
}
return input.type == 'text';
})();
if(supportsChangingTypeAttribute) {
// do stuff...
}
</code></pre>
</li>
<li>
<p>Wrap the statement that IE chokes on in a try…catch construct and in the catch(e) {…} block create a new input element, copy <strong>everything</strong> (where everything is <strong>at least</strong>: attributes, properties, event handlers - both traditional ones <strong>and</strong> listeners) from the password field into it (except the type attribute of course!) and replace the original password field with it. After the first time, the text field could also be reused, to improve performance. If you have a shortage of trouble in your life, you may attempt it, I currently do not. :P It can be a <strong>very</strong> simple task for particular cases, but a generic solution that would work in any site (or even in most sites) seems a really daunting, tedious and downright boring task. I also hope there might be a better solution, that I haven’t thought of. Any ideas?</p>
</li>
</ul>
Idea: The simplest registration form ever2009-07-23T00:00:00Zhttps://lea.verou.me/?p=361<p>If a web application has some sort of registration system (and most do), the registration page should be one of the most attractive, inviting, usable pages of it. It should make you to <strong>want</strong> to register, not repulse you. We don’t want the user to give up in the middle of filling it because they are fed up with it’s length or bad usability, or -even worse- not even attempt to do so, do we?</p>
<p>The most popular websites usually take this rule to heart and employ the simplest registration forms: Only the basic fields required, and most of the times, even without password/email confirmation.</p>
<p>I was wondering lately - what would be the simplest possible registration form? It should have the minimum number of fields required: Username and password and a field for some kind of human verification.</p>
<p>At this point, some readers might argue “Hey, why not an email field as well?”. In my opinion, the email is not always a required field. Let’s see why it’s being asked for in most cases: Unique identification (to prevent double accounts) and for sending out notifications for important events. However, it’s useless for the first purpose due to all these disposable email websites. As for the second purpose, since notifications can be switched off (and if not, then they are essentially considered spam), it could be regarded optional and we don’t include optional fields in registration forms, do we? ;-)</p>
<p>Of course, in websites that use the email instead of a username to let their users log in, you may just substitute the username field above with an email field (since in that case, the username is what could be considered optional) and we also have two fields. Smart readers might have noticed another pattern here: The only fields that are truly required for a registration form are the same ones that are required for a login form plus a human verification field.</p>
<p>And then it dawned on me: We can make the registration process almost as quick as logging in! We could use the same form for both actions. The submit button label could indicate the dual nature of the form, for instance “Log in or register”. If the username (or email) doesn’t exist, we could then ask the user whether they want to create a new account and present them the human verification field at that point. There is no need for a password verification field, since <a href="http://lea.verou.me/2009/06/on-password-masking-and-usability/">we could just have a checkbox for displaying what the user typed</a>, if they feel insecure about it.</p>
<p>I find 3 inherent issues with this approach:</p>
<ol>
<li>Security. If a login attempt fails, the user will know whether he got the username or the password wrong. However, in most websites, you can easily check whether a username exists anyway, so I don’t consider this a real concern. I just included it because I’m certain that if I didn’t, somebody would point it out to me in the comments.</li>
<li>Despite being a more usable approach by nature, it’s not by any means a convention yet. Until it becomes one, I’m afraid that some users will be confused by its extreme …simplicity! Funny, isn’t it?</li>
<li>We won’t be able to employ Ajax verification for the registration form, since it will essentially be a login form as well, and until the user submits, we won’t know what they plan to do (login or register). Having an Ajax verification script there by default will confuse existing users as hell (as in <em>“What do they mean by ‘Username is taken’? WTF??”</em>). So, we have to sacrifice some usability to gain some usability. The question is: Is the usability we gain more than the usability we sacrifice? What do you think?</li>
</ol>
<p>As you can see by the problems mentioned above, it’s still a rough-on-the-edges idea (I just thought about it and I haven’t refined it yet), but I think it’s interesting. What are your thoughts?</p>
Bevels in CSS32009-07-23T00:00:00Zhttps://lea.verou.me/?p=368<p>Yeah, yeah I know, bevels are soooo 1996. And I agree. However, it’s always good to know the capabilities of your tools. Talented designers will know when it’s suitable to use a certain effect and incapable ones will abuse whatever is given to them, so after a lot of thought, I decided to blog about my discovery.</p>
<p>Even though not directly mentioned in the spec, CSS3 is capable of easily creating a bevel effect on any element. Moreover, if the element has rounded corners, the bevel follows that as well. Before explaining the technique, let’s think about how a bevel actually gets drawn. It’s essentially two inner shadows, that when combined, create the illusion of a 3d appearance: a light one from the top left corner and a dark one from the bottom right corner. CSS3 includes the ability to create inner shadows, if you specify the keyword “inset” in the box-shadow declaration (currently only supported by Firefox 3.5). Moreover, the CSS3 spec allows for multiple box shadows on the same elements.</p>
<p>Now, let’s examine an example (only works in Firefox 3.5):</p>
<p>button {
background:#f16;
color:white;
padding:6px 12px 8px 12px;
border:none;
font-size:18px;
-moz-border-radius:10px;
-moz-box-shadow: -2px -2px 10px rgba(0,0,0,.25) inset, 2px 2px 10px white inset;
}</p>
<p>which produces this result:</p>
<p><img src="https://lea.verou.me/2009/07/bevels-in-css3/images/css3bevel.png" alt="css3bevel" /></p>
<p>If we want, we can also create a “pressed” button state, in a similar fashion:</p>
<p>button:active {
-moz-box-shadow: 2px 2px 10px rgba(0,0,0,.25) inset, -2px -2px 10px white inset;
padding:7px 11px 7px 13px;
}</p>
<p border:="" 0;="">button::-moz-focus-inner</p>
<p>which produces this pressed state:</p>
<p><img src="https://lea.verou.me/2009/07/bevels-in-css3/images/css3bevel_pressed.png" alt="css3bevel_pressed" /></p>
<p>See it in action here (only for Firefox 3.5): <a href="http://lea.verou.me/demos/css3bevel.html" title="Linkification: http://lea.verou.me/demos/css3bevel.html">http://lea.verou.me/demos/css3bevel.html</a></p>
<p>Of course, if implemented in a real world website, you should also add the -webkit- and -o- CSS3 properties to provide a closer effect for the other browsers and be ready for the time when the ones that aren’t implemented yet in them will finally make it (for instance, when Webkit implements inset box shadows, it will work in it as well).</p>
<p>Enjoy <strong>responsibly</strong>. :-)</p>
(byte)size matters2009-07-31T00:00:00Zhttps://lea.verou.me/?p=378<p>Yesterday, I was editing a CSS file and I was wondering how many bytes/KB would a particular addition add to it, in order to decide if it was worth it. Since, I had found myself wondering about the exact same thing multiple times in the past, I decided to make a simple standalone HTML page that would compute the size of any entered text in bytes, KB, MB, etc (whatever was most appropriate). It should be simple and quick and it should account for line terminator differences across operating systems.</p>
<p>About half an hour later, I was done. And then it dawned on me: Someone else might need it too! Since .com domains are, so cheap, hey, let’s get a domain for it as well! There are <a href="http://kottke.org/08/02/single-serving-sites">several</a> <a href="http://dowebsitesneedtolookexactlythesameineverybrowser.com/">sites</a> <a href="http://amiawesome.com/">with</a> <a href="http://r33b.net/">a</a> <a href="http://isitchristmas.com/">domain</a> <a href="http://justfuckinggoogleit.com/">that</a> <a href="http://www.sometimesredsometimesblue.com/">are</a> <a href="http://www.d-e-f-i-n-i-t-e-l-y.com/">way</a> <a href="http://www.tired.com/">simpler</a> than that anyway. A friend that was sitting next to me suggested “<a href="http://sizematters.com/">sizematters.com</a>” as a joke, but as it turned out, <a href="http://bytesizematters.com/" title="(byte)size matters!">bytesizematters.com</a> was free, so we registered it. And there it is, less than a day after, <a href="http://bytesizematters.com/">it’s aliiive</a>. :P</p>
<p>Any feedback or suggestions are greatly welcome!</p>
<p>For instance, should I implement a very simple minification algorithm and display bytesize for that as well, or is it too much and ruins the simplicity of it without being worth it? <strong>[edit: I did it anyway]</strong></p>
<p>Should I implement a way to compare two pieces of text and find out the difference in byte size (could be useful for JavaScript refactoring)? <strong>[edit:</strong> <strong>I did it anyway</strong>**]**</p>
Exploring CSS3 text-shadow2009-09-13T00:00:00Zhttps://lea.verou.me/?p=389<p>I consider CSS3’s <code>text-shadow</code> one of the most exciting CSS3* properties, which offers us a lot more effects than it’s name suggests. Of course, it can be used for creating drop shadows for text, and it carries out that task very well, but it’s inherent flexibility allows it to be also used for glow effects, outlines, bevels, extruded text, inset text, fuzzy text and many others (until browser bugs and backwards compatibility come into play… :(). This post is about various findings of mine (and others’, where a source is provided) regarding this property, including browser bugs and inconsistencies, effects that can be achieved with it, compatibility woes etc.</p>
<h3 id="browser-support" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#browser-support">Browser support</a></h3>
<ul>
<li>Opera 9.5+</li>
<li>Firefox 3.5+</li>
<li>Safari 1.0+</li>
<li>Google Chrome</li>
</ul>
<h3 id="text-shadow-syntax" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#text-shadow-syntax">text-shadow syntax</a></h3>
<p>The syntax is fairly simple:</p>
<pre><code class="language-css">text-shadow: <offset-x> <offset-y> <blur-radius> <color>;
</code></pre>
<p>There are some variations (the color could be first instead of last, the blur radius can be omitted if it’s equal to zero and the color may be omitted if it’s the same as the text color) and you may include multiple comma delimited shadows.</p>
<p>You may read more about the syntax in the official <a href="http://www.w3.org/TR/css3-text/#text-shadow">specification</a>.</p>
<h3 id="how-it-works" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#how-it-works">How it works</a></h3>
<p>It helps if you imagine the algorithm for drawing the text shadow as follows:</p>
<ol>
<li>Create a (most of the times differently colored) clone of the text and place it behind the text.</li>
<li>Move it according to the X and Y offsets (positive values move it to the right and bottom respectively)</li>
<li>If a blur radius is specified and it’s > 0, blur it accordingly (the specification doesn’t mention the blurring algorithm to be used, so each browser vendor may choose any blurring algorithm they prefer, and judging by my experiments, it seems they took advantage of this freedom). <strong>In all cases however, the bounding box of the blurred text can extend no further than the bounding box of the original text plus (+) the specified blur radius on each side.</strong></li>
<li>Repeat for the rest of the shadows, if more than 1 are specified. The order in which shadows are drawn seems to be a subject of debate, judging by the wording of the specification and the various existing implementations.</li>
</ol>
<h3 id="the-experiments" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#the-experiments">The experiments</a></h3>
<p>You will find the experiments I performed <a href="http://lea.verou.me/demos/text-shadow.html">here</a>. I tried to come up with (or find) interesting uses of the property. I also tried to make some of them “pretty”, so they could be useful to others, but given the fact that these were primarily created for testing purposes, this wasn’t achievable for all of them. Next to each experiment is the CSS used to produce the effect (directly fetched from the <code><style></code> tag via JavaScript). You’d better not view it with IE until you read below or you might have some freaky nightmares tonight :P</p>
<p>Screenshots from various browsers: (mouse over the thumbnails to see which browser was used for each one)</p>
<p>[gallery link=“file”]</p>
<h3 id="browser-bugs-and-inconsistencies" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#browser-bugs-and-inconsistencies">Browser bugs and inconsistencies</a></h3>
<p>Apparently, some browser bugs were exposed in these experiments:</p>
<ul>
<li><strong>Opera 10</strong> and <strong>Safari</strong> don’t display the shadow when the text color is <code>transparent</code> (demonstrated in Experiment #5). <strong>Opera 9.6</strong> doesn’t seem to support <code>transparent</code> as a text color, so it ignores it.</li>
<li>When the text color is RGBA, <strong>Safari</strong> applies transparency to the shadow, equal to the Alpha component (demonstrated in Experiment #8).</li>
<li><strong>Opera</strong> paints the shadows in the order they were specified, whereas all others use the reverse. According to the current version of the specification, Opera is the only correct one, but I doubt that web designers will give her credit for it :p (Experiment #8)</li>
<li><strong>Google Chrome</strong> uses a crappy blurring algorithm (Experiments #5 and #7)</li>
<li><strong>Safari</strong> and <strong>Chrome</strong> don’t default to the text color when no color is specified in text-shadow, but to <code>transparent</code>. (Experiment #2)</li>
<li><strong>Opera</strong> is seriously messed up when it comes to transparent shadows, as demonstrated by Experiment #9. I can’t even describe the bug (try messing with the text-shadow value a bit and you’ll see why…). Luckily, I can’t think of a single case where a transparent text-shadow would be useful :P</li>
<li>You can see a bit of the shadow in <strong>Google Chrome</strong> even if the offsets and blur radius are all 0 (Experiment #9). I’m not sure if this is a bug, but it’s inconsistent with the other implementations.</li>
<li>Even if you ignore the bugs above, there are slight rendering variations when multiple blurred shadows are involved (or they are more apparent in those cases), as demonstrated by experiments #2, #6 and #7.</li>
</ul>
<p>Firefox’s implementation seems to be the clear winner here…</p>
<p>A note about the above observations: When no version number is mentioned, 3.5 is implied for Firefox, 10 for Opera and 4 for Safari and Chrome.</p>
<h3 id="alternatives-to-text-shadow" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#alternatives-to-text-shadow">Alternatives to text-shadow</a></h3>
<h4 id="ie-filters" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#ie-filters">IE Filters</a></h4>
<p>As you might have noticed, I have managed to completely avoid mentioning Internet Explorer up to this point. It’s no surprise that our dearest browser doesn’t support the text-shadow property. However, it does support some filters (<a href="http://msdn.microsoft.com/en-us/library/ms532985%28VS.85%29.aspx">DropShadow</a> and <a href="http://msdn.microsoft.com/en-us/library/ms533086%28VS.85%29.aspx">Shadow</a>) that could be used to provide a very small subset of the different kinds of text shadows, although they severely <strong>mess up the font anti-aliasing</strong> (just like all IE filters). Also, if the parent or siblings of the text node in question have backgrounds or borders <strong>an extra element is needed to enclose the text node</strong> (you’ll see in the experiments why…). For these reasons, I highly doubt whether they are worth it and I don’t use them personally. However, if you are interested you can see a brief demonstration of these two filters in Experiments #3 (DropShadow) and #6 (Shadow, actually 4 of them).</p>
<h3 id="the-%3Abefore-pseudo-element" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#the-%3Abefore-pseudo-element">The :before pseudo-element</a></h3>
<p>This could be used instead of the text-shadow, when the blur radius is 0, since browser support for the :before pseudo-element is better than browser support for text-shadow (even IE8 supports that, yay). <a href="http://www.workingwith.me.uk/articles/css/cross-browser-drop-shadows">Here is a thorough (although slightly outdated) tutorial on this technique</a>. However,this workaround severely hurts separation of presentation and content/structure, since the content has to be duplicated in the CSS. Repeating something greatly increases the chance that the two copies become inconsistent, since people tend to be forgetful. Also, you have to know in advance the exact height of the text (in lines), another maintenance headache. For these reasons, I don’t use this workaround either.</p>
<p>In my humble opinion, the text shadow is usually just icing on the cake and not something crucial to the design, so it doesn’t hurt if it won’t show in some old and/or crappy browsers. It degrades gracefully in most cases (ok, you’ll have to wait a few years before using it in ways that don’t) so it doesn’t hurt usability/accessibility either. It’s just one of the little treats I like to offer to visitors that were smart enough to use a decent browser. :-)</p>
<h3 id="epilogue" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/09/css-text-shadow/#epilogue">Epilogue</a></h3>
<p>text-shadow is a very flexible property, with probably the best browser and editor – even Dreamweaver acknowledges it’s existence! – support among all notable CSS3* properties. It also degrades gracefully most of the times (the experiments above shouldn’t be considered “most of the times”! :P ) and this is why it’s probably also the most widely used CSS3* property.</p>
<p>I think it could be improved even more by allowing for the inset keyword (just like inset box-shadows – sadly only Firefox 3.5 supports those at the time) and a fourth parameter could be used to enlarge or shrink the shadow (currently the only way to enlarge it is by blurring it, which isn’t always desirable) although it would complicate the shorthand (the blur radius would probably become required – so that the browser can tell them apart). However, a separate property could be used to solve that (text-shadow-size?). I guess we could combine the :before technique, with transparent text color (in the :before pseudo-element) and a text-shadow for that to imitate such an effect (I can elaborate if this seems obscure) although I haven’t actually tried it (however, even if it works, it’s too much of a hassle).</p>
<p>Anyway, I guess it’s too late for such suggestions, so let’s focus on what we actually will get (?) which is more than sufficient :-)</p>
<p>______________________________________________________________</p>
<p>*Actually, it was originally proposed for CSS 2.1, but it was dropped due to lack of implementations (basically only Webkit supported it)</p>
A CSS3 learning(?) tool2009-10-08T00:00:00Zhttps://lea.verou.me/?p=414<p>In case anyone is interested, <a href="http://lea.verou.me/scripts/css3learn.html">this is my take</a> on the <a href="http://ajaxian.com/archives/interactive-css-3-generator">“challenge” that Brad Neuberg posted today on Ajaxian</a>. It needs more properties, but it’s very easy to extend. I guess I should also add CSS3 values (RGBA/HSL(A) colors, CSS gradients etc) but oh well, I’m currently in a hurry. I will, if anyone actually finds it useful (?).</p>
<p>It didn’t prove much of a challenge actually and I honestly doubt it’s educational value (actually it’s value in general), but it was an interesting thing to do while drinking my first coffee in the morning – I really enjoyed writing it :)</p>
New version of rgba.php is out!2009-10-25T00:00:00Zhttps://lea.verou.me/?p=418<p><a href="http://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/">It’s been a while since I posted my little server-side solution for cross-browser RGBA colors</a> (in a nutshell: native rgba for the cool browsers that support it, a PHP-generated image for those that don’t). For features, advantages, disadvantages etc, go see the original post. In this one I’ll only discuss the new version.</p>
<p>So, since it’s release I’ve received suggestions from many people regarding this script. Some other ideas were gathered during troubleshooting issues that some others faced while trying to use it. I hope I didn’t forget anything/anyone :)</p>
<h3 id="changelog-(%2Bcredits)%3A" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/10/new-version-of-rgba-php-is-out/#changelog-(%2Bcredits)%3A">Changelog (+credits):</a></h3>
<ol>
<li>You may now specify the size of the generated image (thanks <em><strong><a href="http://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#comment-122">Sander Arts</a></strong></em>!)</li>
<li>If the PHP version is below 5.1.7 the call to imagepng() uses 2 parameters instead of 4, to workaround the bug found by <em><strong><a href="http://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/#comment-103">Bridget</a></strong></em> (thanks <strong><em>Chris Neale</em></strong> for suggesting the use of phpversion()!)</li>
<li>Added error_reporting() to only allow for fatal errors and parse errors to go through (I should had done this anyway but I completely forgot). This solves an issue that <strong><em>Erin Doak</em></strong> pointed out, since they had set up notices to be displayed and even a reference to an undefined index made the whole script collapse.</li>
<li><em><strong>Mariotti Raffaele</strong></em> pointed out that apache_request_headers() was not defined in all PHP installations. After looking into it a bit, I found out that it’s available only when PHP is installed as an Apache module. After some more research it turned out that the only way to get the If-Modified-Since header otherwise is an .htaccess, so I ruled that out (It would complicate the workaround I think and I doubt all hosts allow .htaccess (?). On the other hand, an .htacess would also allow for some URL rewriting goodness… Hmmm… Should I consider this?). So, if the function is not available, it serves the file with an 200 response code every time, instead of just sending a 304 response when the If-Modified-Since header is present.</li>
<li><a href="http://lea.verou.me/2009/10/new-version-of-rgba-php-is-out/#comment-893"><strong>Igor Zevaka</strong></a> for pointing out that the Expires header wasn’t a valid HTTP date.</li>
</ol>
<h3 id="links" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2009/10/new-version-of-rgba-php-is-out/#links">Links</a></h3>
<p><strong><a href="https://lea.verou.me/2009/10/new-version-of-rgba-php-is-out/uploads/rgba.zip">rgba.php</a></strong></p>
<p><strong><a href="http://lea.verou.me/wp-content/themes/leaverou/images/rgba.php?r=255&g=0&b=100&a=80">Demo</a></strong></p>
<p>Enjoy :) and please report any bugs!</p>
A different approach to elastic textareas2009-11-13T00:00:00Zhttps://lea.verou.me/?p=425<p>I loved elastic textareas since the very first moment I used one (at facebook obviously). They let you save screen real estate while at the same time they are more comfortable for the end user. It’s one of the rare occasions when you can have your UI cake and eat it too!</p>
<p>However, I never liked the implementation of the feature. In case you never wondered how it’s done, let me explain it in a nutshell: All elastic textarea scripts (or at least all that I know of) create a hidden (actually, absolutely positioned and placed out of the browser window) div, copy some CSS properties from the textarea to it (usually padding, font-size, line-height, font-family, width and font-weight) and whenever the contents of the textarea change they copy them to the hidden div and measure it’s dimensions. It might be good enough for facebook, where the styling of those textareas is fairly simple and consistent throughout the site, or any other particular site, but as a generic solution? I never liked the idea.</p>
<p>So, I tried to explore a different approach. As Andrea Giammarchi <a href="http://webreflection.blogspot.com/2009/11/google-closure-im-not-impressed.html">recently wrote</a> <em>“This is almost intrinsic, as developers, in our DNA: we spot some interesting concept? We rewrite it from scratch pretending we are doing it better!”</em> and I’m no exception (although in this case I don’t think I did it better, I just think it has potential). The basic idea is quite naive, but it works quite well in most browsers (Internet Explorer being the black sheep as usual): Test if the textarea is scrollable, and if so, increase it’s <code>rows</code> attribute and try again. If it’s not scrollable initially, try decreasing it’s <code>rows</code> attribute until it becomes scrollable (and then ++ it).</p>
<p>It works flawlessly on Firefox and quite well on Safari, Chrome and Opera (it just slightly twitches when it enlarges in those). Stupid Internet Explorer though repaints too many times, causing a flicker at the bottom when the user is typing, something really disturbing, so I can’t consider the script anything above <strong>experimental</strong> at the moment. I’m just posting it in case anyone has an idea of how to fix the aforementioned issues, because apart from those it has quite a few advantages:</p>
<ul>
<li>Should work with any CSS styles</li>
<li><strong>No</strong> library requirements (unlike all the others I know of)</li>
<li>Only <strong>800 bytes</strong> minified (2.4KB originally)</li>
</ul>
<p>So, here it is:</p>
<ul>
<li><a href="http://lea.verou.me/scripts/elastic-textarea/">Demo</a></li>
<li><a href="http://lea.verou.me/scripts/elastic-textarea/elastic-textarea.js">elastic-textarea.js</a></li>
<li><a href="http://lea.verou.me/scripts/elastic-textarea/elastic-textarea-min.js">elastic-textarea-min.js</a></li>
</ul>
<p><em>For the record, I <strong>don’t</strong> think that a script <strong>should</strong> be needed for things like that. This looks like something that should be handled by CSS alone. We basically want the height of an element to adjust as necessary for it’s contents to fit. We already use CSS for these things on other elements, why not form controls as well?</em></p>
Yet another email hiding technique?2009-11-29T00:00:00Zhttps://lea.verou.me/?p=443<p>While <a href="http://lea.verou.me/2009/11/exploring-browser-supported-unicode-characters-and-a-tweet-shortening-experiment/">exploring browser-supported Unicode characters</a>, I noticed that apart from the usual @ and . (dot), there was another character that resembled an @ sign (0xFF20 or @) and various characters that resembled a period (I think 0x2024 or ․ is closer, but feel free to argue).</p>
<p>I’m wondering, if one could use this as another way of email hiding. It’s almost as easy as the foo [at] bar [dot] com technique, with the advantage of being far less common (I’ve never seen it before, so there’s a high chance that spambot developers haven’t either) and I think that the end result is more easily understood by newbies. To encode <a href="mailto:foo@bar.com" title="Linkification: mailto:foo@bar.com">foo@bar.com</a> this way, we’d use (in an html page):</p>
<p>foo@bar․com</p>
<p>and the result is: foo@bar․com</p>
<p>I used that technique on the <a href="http://lea.verou.me/demos/ligatweet/#conversions">ligatweet page</a>. Of course, if many people start using it, I guess spambot developers will notice, so it won’t be a good idea any more. However, for some reason I don’t think it will ever become that mainstream :P</p>
<p>By the way, if you’re interested in other ways of email hiding, <a href="http://www.csarven.ca/hiding-email-addresses">here’s an extensive article on the subject</a> that I came across after a quick googlesearch (to see if somebody else came up with this first – I didn’t find anything).</p>
Exploring browser-supported Unicode characters and a tweet shortening experiment2009-11-29T00:00:00Zhttps://lea.verou.me/?p=438<p>I recently wanted to post something on twitter that was just slightly over the 140 chars limit and I didn’t want to shorten it by cutting off characters (some lyrics from Pink Floyd’s “Hey You” that expressed a particular thought I had at the moment – it would be barbaric to alter Roger Waters’ lyrics in any way, wouldn’t it? ;-)). I always knew there were some ligatures and digraphs in the Unicode table, so I thought that these might be used to shorten tweets, not only that particular one of course, but any tweet. So I wrote <a href="http://lea.verou.me/scripts/unicode.html" title="Browser supported unicode characters">a small script</a> (warning: very rough around the edges) to explore the Unicode characters that browsers supported, find the replacement pairs and build the tweet shortening script (I even thought of a name for it: ligatweet, LOL I was never good at naming).</p>
<p>My observations were:</p>
<ul>
<li>Different browsers support different Unicode characters. I think Firefox has the best support (more characters) and Chrome the worst. By the way, it’s a shame that Chrome doesn’t support the Braille characters.</li>
<li>The appearance of the same characters, using the same font has huge differences across browsers. A large number of glyphs are completely different. This is very apparent on dingbats (around 0x2600-0x2800).</li>
<li>For some reason unknown to me, hinting suffers a great deal in the least popular characters (common examples are the unit ligatures, like ㏈ or ㎉). Lots of them looked terribly unlegible and pixelated in small sizes (and only in small sizes!!). Typophiles feel free to correct me if I’m mistaken, but judging by my brief experience with font design, I don’t think bad hinting (or no hinting at all) can do that sort of thing to a glyph. These characters appeared without any anti-aliasing at all! Perhaps it has to do with Cleartype or Windows (?). If anyone has any information about the cause of this issue, I would be greatly interested.</li>
<li>It’s amazing what there’s in the Unicode table! There are many dingbats and various symbols in it, and a lot of them work cross browser! No need to be constrained by the small subset that html entities can produce!</li>
</ul>
<p>The tweet shortening script is here: <a href="http://lea.verou.me/demos/ligatweet/" title="Linkification: http://lea.verou.me/demos/ligatweet/">http://lea.verou.me/demos/ligatweet/</a></p>
<p>I might as well write a bookmarklet in the future. However, I was a bit disappointed to find out that even though I got a bit carried away when picking the replacement pairs, the gains are only around 6-12% for most tweets (case sensitive, of course case insensitive results in higher savings, but the result makes you look like a douchebag), but I’m optimistic that as more pairs get added (feel free to suggest any, or improvements on the current ones) the savings will increase dramatically. And even if they don’t I really enjoyed the trip.</p>
<p>Also, exploring the Unicode table gave me lots of ideas about scripts utilizing it, some of which I consider far more useful than ligatweet (although I’m not sure if I’ll ever find the time to code them, even ligatweet was finished because I had no internet connection for a while tonight, so I couldn’t work and I didn’t feel like going to sleep)</p>
<p>By the way, In case you were wondering, I didn’t post the tweet that inspired me to write the script. After coding for a while, It just didn’t fit my mood any more. ;-)</p>
Reading cookies the regular expression way2009-12-03T00:00:00Zhttps://lea.verou.me/?p=460<p>While taking a look on <a href="http://24ways.org/2009/breaking-out-the-edges-of-the-browser">the 2nd 24ways article for 2009</a>, I was really surprised to read that <em>“The Web Storage API is basically cookies on steroids, a unhealthy dosage of steroids. Cookies are always a pain to work with. First of all you have the problem of setting, changing and deleting them. <strong>Typically solved by Googling and blindly relying on PPK’s solution.</strong>”</em> (bold is mine)</p>
<p>Of course, there’s nothing wrong with <a href="http://www.quirksmode.org/js/cookies.html#script">PPK’s solution</a>. It works just fine. However, I always thought his readCookie() function was too verbose and complicated for no reason. It’s a very common example of someone desperately trying to avoid using a regular expression. I googled for “<a href="http://www.google.com/search?q=javascript+read+cookie">javascript read cookie</a>” and to my surprise, all examples found in the first results were very similar. I never understood why even experienced developers are so scared of regular expressions. Anyway, if anyone wants a shorter function to read a cookie, here’s what I use:</p>
<p>function readCookie(name) {
// Escape regexp special characters (thanks kangax!)
name = name.replace(/([.*+?^=!:${}()|[\]\/\\])/g, ‘\\$1’);</p>
<p>var regex = new RegExp(‘(?:^|;)\\s?’ + name + ‘=(.*?)(?:;|$)’,‘i’),
match = document.cookie.match(regex);</p>
<p>return match && unescape(match[1]); // thanks James!
}</p>
<p><strong>Update:</strong> <strong>Function updated, see comments below</strong>.</p>
<p>I’ve been using it for years and it hasn’t let me down. :)</p>
<p>Probably lots of other people have come up and posted something similar before me (I was actually very surprised that something like this isn’t mainstream), but I’m posting it just in case. :)</p>
Quickly find the Gravatar that corresponds to a given email2009-12-07T00:00:00Zhttps://lea.verou.me/?p=470<p>Today I needed to quickly find the Gravatars that corresponded to a bunch of email addresses for some reason (don’t ask). After a bit of googling and wandering around in <a href="http://gravatar.com/">Gravatar’s official site</a> and others, I firgured out it’s probably much quicker to write a short PHP script for that myself, than keep looking.</p>
<p>Here it is, in case someone ever needs to do something similar: (click on the screenshot)</p>
<p><a href="http://lea0.verou.me/demos/gravatar.php"><img src="https://lea.verou.me/2009/12/quickly-find-the-gravatar-that-corresponds-to-a-given-email/images/gravatar.png" alt="Quickly find the Gravatar that corresponds to a given email" /></a></p>
<p>(has anyone noticed my latest love affair with Helvetica/Arial? :P )</p>
Redesign2010-02-13T00:00:00Zhttps://lea.verou.me/?p=482<p>Was about time, wasn’t it?</p>
<p>I wanted a simpler, more minimalistic (and wider!) theme for a while now. The other one was too restrictive. I had designed it when I had absolutely no content, and few changes were made to it afterwards.</p>
<p>So, today that I was too sad and furious to do anything productive, I spent a few hours redesigning the blog (creative venting…). Please note that it’s just a few hours’ work (with no mockup), so it’s bound to be a bit rough around the edges. I will refine it more as time goes by.</p>
<p>(and just like the previous one, it’s best viewed in more CSS3-supporting browsers, like Firefox, Chrome or Safari. If we can’t use the latest bells n’ whistles in our <strong>personal</strong> blogs, where can we? ;))</p>
<p>Here’s a screenshot from the previous theme:</p>
<p><img src="https://lea.verou.me/2010/02/redesign/images/screenshot.png" alt="Screenshot of the old lea.verou.me theme" title="The old lea.verou.me theme" /></p>
<p>R.I.P. my first wordpress theme.</p>
<p>PS: Yeah, I know I haven’t posted in a while. I have started lots of posts, but didn’t finish any. I hope I’ll have something complete to post soon.</p>
iPhone keyboard with CSS3 -- no images2010-02-18T00:00:00Zhttps://lea.verou.me/?p=490<p>Yeap, this is yet another of those things that make no practical sense but are fun to make just to see whether it can actually be done. It’s also a proof of the fact that when I have too many things to do, I tend to procrastinate more. :P</p>
<p>Here it is (resize the window to get the narrow version ;)):</p>
<p><a href="http://lea.verou.me/demos/iphone-keyboard/">http://lea.verou.me/demos/iphone-keyboard/</a></p>
<p>It should look correct in <strong>Firefox 3.6, Chrome 4 and Safari 4</strong>. It looks best on Firefox 3.6 due to it’s ability to render subpixel distances, whereas other browsers just round everything to the closest pixel. It also looks best in computers with <strong>Helvetica</strong> installed (it’s installed by default on macs btw) but it should look sufficiently OK with Arial too, since it’s a rip-off of Helvetica ;) (the only problem with Arial is that the line-height of the buttons with the symbols will be slightly different since the custom font’s measurements are based on Helvetica Bold) Also, ironically, it doesn’t look ok in the iPhone!</p>
<p>For those of you that don’t use one of the aforementioned browsers as your primary and are way too bored to switch (or don’t even have them installed (!)), here are two screenshots from Firefox 3.6 (nicely cropped to only contain the keyboard):</p>
<figure class="center">
<img src="https://lea.verou.me/2010/02/iphone-keyboard-with-css3-no-images/images/css_wide_keyboard.png" alt="" />
<figcaption>Screenshot of the wide version</figcaption>
</figure>
<figure class="center">
<img src="https://lea.verou.me/2010/02/iphone-keyboard-with-css3-no-images/images/css_narrow_keyboard.png" alt="" />
<figcaption>Screenshot of the narrow version</figcaption>
</figure>
<p>As for how it’s done, as you can easily see, most of it is run-of-the-mill for someone with a decent grasp on CSS3: media queries, CSS gradients, shadows, border-radiuses and RGBA. The only tricky part is the symbols for shift, backspace and international. I have to admit I cheated a bit here: I didn’t use images, but I used @font-face with a custom font that just contains these 3 symbols. The reasons behind that are that this way I wouldn’t have to create 2 versions of the symbols (light and dark, for pressed and normal states respectively) and that they are vector, so they scale (try zooming in).</p>
<p>Please note that there’s no functionality attached to it. It’s just an interface. I wasn’t interested at making an on-screen keyboard in general, I was just interested to see if a keyboard visually identical to iPhone’s is possible with CSS alone. If someone wants to actually use it and/or develop it further, you’re free to do so, as long as you keep the comment at the start of the css file. ;)</p>
<p>An interesting discussion about this could be “What would be the ideal markup to semantically style a keyboard?”. Personally, I just paid attention to the more pragmatic objectives of making the keys focusable, and keeping the complexity of the DOM tree to a minimum, so you might find it semantically wrong (I used a </p><ul> for the container, <li>s for the rows and <button>s for the keys) – but what is right actually in this case? Is a keyboard a list or a table of keys? I don’t think so…<p></p>
</button></li></ul>CSSNinja's custom forms revisited to work with CSS sprites2010-02-19T00:00:00Zhttps://lea.verou.me/?p=512<p>I read today <a href="http://www.thecssninja.com/">CSS Ninja</a>’s (Ryan Sheddon’s) <a href="http://www.thecssninja.com/css/custom-inputs-using-css">brilliant new technique about the creation of custom checkboxes and radio buttons with CSS alone</a>.</p>
<p>The only thing that bugged me about it was something he pointed himself out as well:</p>
<blockquote>
<p>This technique has only 1 drawback I can think of, IE support is not a drawback for me, you can’t use a big sprite image to save all the radio and checkbox states, they need to be individual images. Using CSS generated content to insert an image doesn’t give you control of the image position like a background image does.</p>
</blockquote>
<p>And then I wondered “but hey, <strong>why</strong> can’t we use background images?”. It seemed pretty obvious to me that we could combine a transparent text color with normal css sprites and a display of inline-block in the ::before pseudo-element to achieve the exact same effect. I verified that my initial assertion was actually correct, and tested it in Firefox, Chrome, Safari and Opera (the latest only, no time for testing in older ones at the moment) and it seems to work fine.</p>
<p>Here it is: <a href="http://lea.verou.me/demos/cssninja-custom-forms/">demo</a> | <a href="http://lea.verou.me/demos/cssninja-custom-forms/source.zip">source files (including .psd file of the sprite)</a></p>
<p>I’m afraid there’s some blatantly obvious drawback in my approach that made Ryan prefer his method over this, but I’m posting it just in case…</p>
CSS3 structural pseudo-class selector tester2010-03-14T00:00:00Zhttps://lea.verou.me/?p=521<p><a href="https://lea.verou.me/2010/03/css3-structural-pseudo-class-selector-tester/images/Screen-shot-2011-09-20-at-14.13.13-.png"><img src="https://lea.verou.me/2010/03/css3-structural-pseudo-class-selector-tester/images/Screen-shot-2011-09-20-at-14.13.13--300x187.png" alt="" /></a>I was doing some research today about how people explain the CSS3 structural* pseudo classes and I stumbled upon this demo by CSS tricks: <a href="http://css-tricks.com/examples/nth-child-tester/">http://css-tricks.com/examples/nth-child-tester/</a></p>
<p>I thought the idea is <strong>awesome</strong>, but lacks a few features:</p>
<ul>
<li>It doesn’t use the native browser algorithm for selecting the elements. Granted, it’s not that tough to code your own properly, but I trust a browser implementation more (IE doesn’t support these altogether, so it’s out of the question anyway).</li>
<li>Doesn’t allow you to test for nth-last-child, nth-of-type, nth-last-of-type (and especially the last two are a lot harder to understand for most people)</li>
<li>Doesn’t allow you to add/remove list items to see the effects of the selector with different numbers of elements (especially needed if nth-last-child, nth-of-type, nth-last-of-type were involved)</li>
</ul>
<p>So, I decided to code my own. It allows you to test for all 4 nth-something selectors, supports adding/removing elements (the selected elements update instantly) and uses the native browser implementation to select them (so it won’t work on IE and old browsers).</p>
<p>Enjoy: <a href="http://lea.verou.me/demos/nth.html"><strong>CSS3 structural pseudo-class selector tester</strong></a> :)</p>
<p>*Yes, :root and :empty also belong to those, but are rarely used. All other structural pseudoclasses are actually shortcuts to some particular case of the aforementioned 4 :)</p>
MySQL: Are you actually utilizing your indexes?2010-03-25T00:00:00Zhttps://lea.verou.me/?p=527<p><em>This might seem elementary to those of you that are DBAs or something similar, but it was fascinating to find out (not to mention it greatly helped what I had to do), so I decided to post it, in case it helps someone else too.</em> A few moments ago I found out that whereas a query along the lines of…</p>
<pre><code class="language-sql">SELECT title, COUNT(1) as replies
FROM post INNER JOIN thread USING(threadid)
WHERE **UNIX\_TIMESTAMP(NOW()) - post.dateline < 86400**
GROUP BY threadid
ORDER BY replies DESC
LIMIT 5
</code></pre>
<p>took a whopping <strong>~10 seconds</strong> on a post table of around 2,000,000 rows and a thread table of around 40,000 rows, the following:</p>
<pre><code class="language-sql">SELECT title, COUNT(1) as replies
FROM post INNER JOIN thread USING(threadid)
WHERE **post.dateline > UNIX\_TIMESTAMP(NOW()) - 86400**
GROUP BY threadid
ORDER BY replies DESC
LIMIT 5
</code></pre>
<p>took a mere <strong>0.03 seconds</strong>!</p>
<p>Probably, MySQL wasn’t able to utilize the B+ tree index of the dateline column in the first query, whereas in the second, things were a bit more obvious to it. This can also be observed by examining the information about the execution plan that EXPLAIN provides:</p>
<pre><code>mysql> explain select t.threadid, t.title, count(1) as replies from vb3\_post as p inner join vb3\_thread as t using(threadid) where unix\_timestamp(now()) - p.dateline < 86400 group by p.threadid order by replies desc limit 5;
+----+-------------+-------+------+---------------+----------+---------+------------+-------+---------------------------------+
| id | select\_type | table | type | possible\_keys | key | key\_len | re | rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+------------+-------+---------------------------------+
| 1 | SIMPLE | t | ALL | PRIMARY | NULL | NULL | NULL | 39859 | Using temporary; Using filesort |
| 1 | SIMPLE | p | ref | threadid | threadid | 4 | t.threadid | 49 | Using where |
+----+-------------+-------+------+---------------+----------+---------+------------+-------+---------------------------------+
2 rows in set (0.01 sec)
```
```
mysql> explain select t.threadid, t.title, count(1) as replies from vb3\_post as p inner join vb3\_thread as t using(threadid) where p.dateline > UNIX\_TIMESTAMP(NOW()) - 86400 group by p.threadid order by replies desc limit 5;
+----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+
| id | select\_type | table | type | possible\_keys | key | key\_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+
| 1 | SIMPLE | p | range | threadid,dateline | dateline | 4 | NULL | 1171 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | t | eq\_ref | PRIMARY | PRIMARY | 4 | p.threadid | 1 | |
+----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+
2 rows in set (0.00 sec)
```
So, don't rest assured that MySQL will use your indexes every time it should. It seems that sometimes you have to explicitly point it out.
</code></pre>
On CSS counters plus a CSS3 Reversi UI2010-04-01T00:00:00Zhttps://lea.verou.me/?p=540<p>CSS Counters have a lot more potential than most web developers seem to think. The common use case consists of something like:</p>
<pre><code class="language-css">somecontainer { counter-reset: foocount; }
Ε { counter-increment: foocount; }
Ε::before { content: counter(foocount) ". "; }
</code></pre>
<p>commonly used to add numbering to section headings or re-create an </p><ol>'s counters in order to style them (since browser support for ::marker is ridiculous).<p></p>
<p>Have you ever thought of applying the counter to <strong>different</strong> elements than the ones being counted? This way we’re able to count elements and display their total count somewhere with CSS alone! (and with the variety of selectors in CSS3, I see great potential here…). I’m referring to something like:</p>
<pre><code class="language-css">ul { counter-reset:foo; }
li { counter-increment:foo; }
p::after { content:counter(foo); }
</code></pre>
<p>From my tests, this works flawlessly in Firefox, Safari, Opera and Chrome (I’ve only checked the latest stable though), <strong>as long as the element that displays the count comes after the elements being counted (in the markup)</strong>.</p>
<p>Another underutilized aspect of CSS counters (well, far less underused than the above, but still) is how we can combine multiple in the same pseudoelement. For instance, to count rows and cells of a table and display the count inside each cell:</p>
<pre><code class="language-css">table {
counter-reset:row;
}
tr {
counter-increment:row;
counter-reset:cell;
}
td {
counter-increment:cell;
}
td::after {
content:counter(row, upper-alpha) counter(cell);
}
</code></pre>
<p>Which displays counters like A1, A2, A3, B1, B2, B3, etc in the cells. When the content property is more properly implemented, you wouldn’t even need the last rule.</p>
<p>Last but not least, a <a href="http://lea.verou.me/demos/Reversi/">CSS3 Reversi UI</a> (no images used!) I created a while ago that demonstrates the above (and various other things, like --finally-- a use case for <strong>:empty</strong> :P ). <em>Looks</em> fine only in Firefox and Opera 10.5, due to lack of support for inset box shadows in Safari and <a href="https://bugs.webkit.org/show_bug.cgi?id=36939">buggy</a> support in Chrome. <em>Works</em> fine in all 4 of them (IE is out of the question anyway).</p>
<p><a href="http://lea.verou.me/demos/Reversi/"><img src="http://lea.verou.me/demos/Reversi/screenshot.png" alt="Screenshot of the UI" title="Screenshot from Firefox 3.6" /></a></p>
<p>The displayed counts of each player’s pieces (top right corner) are just CSS counters. Same goes for every cell’s name. This is mostly a proof of concept, since it’s impossible to determine if someone won by CSS alone, so we would have to count the pieces in JS too.</p>
<p>As a game it’s not finalized, you are basically only able to play against yourself and it doesn’t know when somebody won, so it’s not very useful or enjoyable. If someone wants to take it up and develop it further be my guest.</p>
<p><strong>Note to avoid confusion:</strong> CSS Counters are <strong>not</strong> CSS 3. They are perfectly valid <strong>CSS 2.1</strong>. The “CSS3” in the title (“CSS3 Reversi”) is due to other techniques used in it’s UI.</p>
</ol>"Wow, Mona Lisa with pure CSS!"2010-05-25T00:00:00Zhttps://lea.verou.me/?p=563<p>There has been a recent flood of CSS experiments that utilize CSS3 features to convert some meaningless markup to spectacular pictures. It all started when <a href="http://desandro.com/articles/opera-logo-css/">David Desandro used CSS3 to draw the Opera logo</a>. This seemed to inspire lots of other folks who created similar demos:</p>
<ul>
<li><a href="http://rathersplendid.net/home/pure-css-icons">Pure CSS icons</a></li>
<li><a href="http://blog.insicdesigns.com/2010/03/create-social-media-icons-in-pure-css/">Create Social Media Icons in pure CSS</a></li>
<li><a href="http://www.romancortes.com/blog/css3-flower/">CSS flower</a></li>
<li><a href="http://desandro.com/resources/curtis-css-typeface/">Curtis CSS typeface</a></li>
<li><a href="http://gabri.me/htmlcss/2010/css3-gradients-coffee-cup/">CSS3 Gradients coffee cup</a></li>
</ul>
<p>I can certainly share their enthusiasm and I am also amazed by their results. Besides that, I think that pushing CSS3 to the edge like that, helps us understand the spec better, which leads us to find and file browser bugs or write comments regarding the spec itself. Filing bugs is crucial at this stage, with all browser vendors gradually adding experimental --and frequently buggy-- CSS3 support to their products. Also, don’t get me wrong: I can easily see the benefits of reducing the number of images in a web application interface (far quicker/easier modifications, less HTTP requests and most of the time, less bandwidth).</p>
<p>However, I’m afraid we’re losing sight of the big picture. These aren’t demos that are or will ever be legitimate CSS use cases. Even after universal CSS3 browser support is achieved, they would (and should) still be considered “hacks”. Almost all the arguments pro their usage also apply to more suitable ways of including images in web applications:</p>
<ul>
<li><strong>Fewer HTTP requests</strong>: Same with any kind of embedded image (data URIs, inline SVG and so on)</li>
<li><strong>Scalable</strong>: Same with SVG and symbols embedded in custom fonts</li>
<li><strong>Easier to modify:</strong> Same with SVG</li>
<li><strong>Less bandwidth (in some cases):</strong> Same with SVG (and it can be cached too, when not inline)</li>
</ul>
<p>And apart from these, these illustrations require non-semantic crap to be included in the markup which, besides issues of theoretical purity, makes it harder for other people to use them.</p>
<p>As for the <strong>graceful degradation</strong> argument, yes, this does only apply to CSS “images”. But in this case, is it really an advantage? I seriously doubt it. People won’t notice rounded corners if they’re missing from an interface, but they’re definitely going to notice a blocky Opera logo. And they’re not used in thinking that their browser has something to do with how an image renders, so they’ll just blame the website.</p>
<p>CSS is supposed to enhance the presentation of a document or interface, not to be (ab)used for the creation of illustrations from scratch. It’s supposed to separate presentation from structure, not generate stuff. There are other technologies that are far more suitable for this (*cough*SVG*cough*). I think we should use our energy and creativity to make CSS3 demos that people will actually use in the future when all this is fully supported, not stuff doomed to be eternally considered hackery.</p>
<p>“Where should we draw the line?” one might ask. For example, is a <a href="http://oddvalue.co.uk/blog/2010/02/css3_clock/">Pure CSS analog clock</a> a CSS <strong>ab</strong>use case? Or even my own <a href="http://lea.verou.me/2010/02/iphone-keyboard-with-css3-no-images/">CSS iPhone keyboard</a>? Now <strong><em>that’s</em></strong> a good question! A rule of thumb seems to be <em>“if it inherently (=not due to browser support issues) involves a bunch of empty (or with meaningless content) HTML elements, then that’s a bad sign”</em> but that might be overly strict. What’s your take on it?</p>
<p><strong><em>Disclaimer:</em></strong> <em>Yes, I’m fully aware that most of the time, such experiments are created just for fun by their (very talented) authors, which are perfectly aware of all the things mentioned above. However, I’ve also grown tired of reading comments by people that seem to to think that “This is the future of the web!”. Let’s hope it’s not.</em></p>
Organizing a university course on modern Web development2010-07-26T00:00:00Zhttps://lea.verou.me/?p=576<p>About a year ago, prof. <a href="http://twitter.com/vassalos">Vasilis Vassalos</a> of <a href="http://aueb.gr/" title="The university official website (yeah, I know...)">Athens University of Economics and Business</a> approached me and asked for my help in a new course they were preparing for their <a href="http://www.cs.aueb.gr/">Computer Science department</a>, which would introduce 4th year undergrads to various web development aspects. Since I was always complaining about how outdated higher education is when it comes to web development, I saw it as my chance to help things change for the better, so I agreed without a second thought.</p>
<p>This is one of the main reasons I didn’t have time to write many blog posts for the past months: This activity took up all my spare time. However, it proved to be an interesting and enlightening experience, in more than one ways. In this blog post I’ll describe the dilemmas we faced, the decisions we made and the insights I gained throughout these 6 months, with the hope that they’ll prove to be useful for anyone involved in something similar.</p>
<h3 id="table-of-contents" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#table-of-contents">Table of contents</a></h3>
<ol>
<li><a href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#course-content">Content</a></li>
<li><a href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#homework">Homework</a></li>
<li><a href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#labs">Labs</a></li>
<li><a href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#aftermath">Personal aftermath</a></li>
</ol>
<h3 id="content" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#content">Content</a></h3>
<p>The goals of a university course differ from the ones of a professional seminar or conference session in many ways, the key one being that most of its students will (professionally) utilize the things they learned in the future and not right after they walk away from class. So, the stuff being taught must be useful even after a couple years have passed. Also, issues of the present might not be issues of the future and what isn’t possible today (due to browser support issues) will probably be tomorrow. These observations led us to decide <strong>against teaching proprietary stuff. Instead, we only included things which come with a specification that has reached a fairly stable state</strong> (with the exception of <strong>very</strong> widespread non-standard stuff, such as <code>innerHTML</code>). We also decided <strong>not to address workarounds and browser incompatibilities at all</strong>, since these would probably be out of date in a few years. Also because, if we teach everything else right, they should be able to learn these by themselves, if needed (we did teach feature detection techniques though, those are timeless ;-)). We also included <strong>many cutting edge topics (CSS3, HTML5, ES5, SVG…) since we believe that they will be necessary tools of the trade tomorrow</strong>. To be pragmatic however, <strong>we did not teach stuff that no browser has implemented yet</strong>, besides perhaps a brief mention.</p>
<p>To make things easier for the students, we used <strong>Firefox 3.6</strong> for everything. We tested their assignments there, we used it to present something in the labs etc. Why Firefox?</p>
<ul>
<li>It’s at a quite good level of standards compliance and implements many modern technologies & features</li>
<li>Fewer bugs (Webkit implements stuff faster, but in more buggy ways)</li>
<li>It has the best development tools (Firebug)</li>
<li>With Brendan Eich being Mozilla’s CTO, we all know how progressive Firefox is when it comes to JavaScript.</li>
</ul>
<p>Of course, this doesn’t mean it’s the only right choice. Google Chrome for example would be another good pick.</p>
<p>Another useful observation was that 4th year Computer Science students already know programming quite well, especially Java. So, <strong>we did not need to go through the basics of programming syntax</strong> like introductory books or seminars frequently do. Consequently, <strong>we skipped explaining how control structures or operators work</strong> in JavaScript or PHP and just <strong>focused on their differences</strong> from Java and other languages.</p>
<p>Another dilemma we faced was whether we should teach stuff on popular frameworks and whether we should allow them in the homeworks. We decided <strong>against allowing them in the homeworks</strong> because I believe that someone must not use a framework just to skip learning about the intricacies of a language. They should be used after the basics have been consolidated, in order to save time. Also because if everyone skips learning and just uses an abstraction to do the heavy lifting from the very beginning, who will write the abstractions after all? Another reason was that a large portion of every JavaScript framework is about handling cross-browser differences. However, these had no place in our course, so a JS framework wasn’t as necessary as it is in day to day web development. Regarding teaching them, <strong>we thought it would be a good idea to introduce students to the popular JS & PHP frameworks in the last lectures</strong>, but there was no time left. Maybe next year.</p>
<p>To sum up, the course content ended up being (I’m listing client-side matters more extensively, since they are also the focus of this blog):</p>
<ul>
<li>General stuff about web application architecture and how the HTTP protocol works</li>
<li>We presented a small web application example (an AJAX shopping cart) in order for the students to get an idea about how everything clicks together</li>
<li>Markup languages
<ul>
<li>SGML</li>
<li>DTDs</li>
<li>HTML and XHTML
<ul>
<li>Basic structure of an (X)HTML document</li>
<li>Content model, block vs inline elements</li>
<li>Basic HTML elements
<ul>
<li>headings & paragraphs</li>
<li>lists (ordered, unordered, definition lists)</li>
<li>tables</li>
<li>grouping elements (div & span)</li>
</ul>
</li>
<li>Doctypes, the HTML5 doctype</li>
<li>The incentives behind XHTML & the future ((X)HTML 5)</li>
<li>(X)HTML Validation</li>
<li>HTML forms
<ul>
<li>How forms work, GET vs POST</li>
<li>Form controls, shared attributes</li>
<li>The various input types (+ the new ones HTML5 brings)</li>
<li>Other form controls (buttons, <code><select></code> lists, textareas)</li>
<li>Basic form accessibility (labels & fieldsets)</li>
</ul>
</li>
<li>Working with Multimedia (old methods, HTML5 video & audio elements, comparison)</li>
</ul>
</li>
<li>XML and XPath, XQuery, XSLT</li>
</ul>
</li>
<li>CSS
<ul>
<li>CSS standards</li>
<li>CSS rules</li>
<li>Validation</li>
<li>Adding CSS to a page (linking/embedding methods)</li>
<li>Media targeting (The media attribute, @media rules, media queries)</li>
<li>CSS selectors
<ul>
<li>Introduction to the DOM</li>
<li>Basic selectors (Universal selector, Type selector, Class selector, Id selector)</li>
<li>Classes vs Ids</li>
<li>Attribute selectors (all 6)</li>
<li>Pseudo-classes (including most of the CSS3 ones)</li>
<li>Pseudo-elements</li>
<li>Simple selectors & simple selector sequences</li>
<li>Combinators (all 4)</li>
<li>Selector grouping</li>
<li>XML namespaces & CSS</li>
</ul>
</li>
<li>Cascading & Inheritance
<ul>
<li>The problem: Conflicts</li>
<li>Specificity</li>
<li>Origin</li>
<li>!important</li>
<li>Inheritance</li>
<li>The special value <em>inherit</em></li>
</ul>
</li>
<li>Properties & values
<ul>
<li>Keywords</li>
<li>Numerical values & units</li>
<li>Colors (including CSS3 colors)</li>
<li>How shorthands work</li>
<li>Unsupported values & providing fallbacks</li>
</ul>
</li>
<li>Box model
<ul>
<li>width & height</li>
<li>Block level & inline level elements (reminder from the HTML lectures)</li>
<li>The <em>display</em> property</li>
<li>border</li>
<li>padding</li>
<li>margin</li>
</ul>
</li>
<li>Positioning
<ul>
<li>The <em>position</em> property</li>
<li>Positioning types (absolute, relative, fixed)</li>
<li>z-index</li>
<li>float</li>
<li>Problems with floats, the <em>clear</em> property</li>
</ul>
</li>
<li>Generated content
<ul>
<li>::before and ::after</li>
<li>Static generated content</li>
<li>Dynamic generated content (attributes & counters)</li>
</ul>
</li>
</ul>
</li>
<li>JavaScript
<ul>
<li>Adding JS to a document</li>
<li>Separation of concerns</li>
<li>A first, annotated, example (a simple script that generates tables of content from <h2> headings)</h2></li>
<li>Basic syntax rules (including semicolons & semicolon insertion)</li>
<li>Variables</li>
<li>Operators (including typeof, the comma operator, strict operators, differences of &&/|| in JS)</li>
<li>Primitives (String, Number, Boolean, null, undefined)</li>
<li>Conversion across primitives</li>
<li>Objects</li>
<li>The <em>in</em> & <em>delete</em> operators</li>
<li>for…in loops</li>
<li>Native objects for primitives (eg the literal 5 vs new Number(5))</li>
<li>The global object</li>
<li>Functions (including function expressions vs function declarations)</li>
<li><em>this</em> & changing execution context</li>
<li>Arrays (including .forEach() traversal)</li>
<li>Regular expressions in JavaScript</li>
<li>OOP in JavaScript
<ul>
<li>OOP concepts in JS</li>
<li>Constructors</li>
<li>Inheritance</li>
<li>Encapsulation (private, priviledged & public properties)</li>
<li>Method overloading</li>
<li>JavaScript shortcomings when it comes to OOP</li>
<li>for…in loops, inherited properties & [[Enumerable]], .hasOwnProperty()</li>
<li>Type detection based on [[Class]] detection (using Object.prototype.toString())</li>
</ul>
</li>
<li>DOM
<ul>
<li>Traversal</li>
<li>Node types</li>
<li>Selecting elements (getElementById, getElementsByClassName, getElementsByName, querySelector, using XPath to select elements)</li>
<li>DOM Manipulation</li>
<li>innerHTML, advantages & criticism</li>
</ul>
</li>
<li>Events
<ul>
<li>Binding & Removing event handlers</li>
<li>Traditional event binding</li>
<li>Capturing & bubbling</li>
<li>Event objects</li>
<li>Event delegation</li>
<li>Firing events</li>
<li>Custom events</li>
<li>What if there’s no mouse?</li>
</ul>
</li>
<li>Client side storage
<ul>
<li>Cookies via HTTP headers, cookies in JavaScript</li>
<li>Problems with cookies</li>
<li>Web storage (localStorage, sessionStorage)</li>
<li>Client-side databases</li>
</ul>
</li>
<li>BOM
<ul>
<li>The window object, window names</li>
<li>Opening new windows</li>
<li>Cross-window communication</li>
<li>Closing windows, Focusing on windows</li>
<li>Cross-origin window communication</li>
<li><em>location</em> & it’s components</li>
<li>The <em>history</em>, <em>screen</em> & <em>navigator</em> objects</li>
<li>User Agent strings</li>
<li>Why you shouldn’t use browser detection</li>
<li>Built-in modal windows (alert, confirm, prompt)</li>
</ul>
</li>
<li>JavaScript & CSS
<ul>
<li>CSS modification (className & classList, inline styles)</li>
<li><em>CSSStyleDeclaration</em> objects</li>
<li>The document.styleSheets collection</li>
<li>Switching stylesheets</li>
<li><em>StyleSheet</em> objects</li>
<li><em>CSSStyleRule</em> objects</li>
<li>Computed style, getting the computed style</li>
</ul>
</li>
<li>Asynchronous execution
<ul>
<li>Timeouts & Intervals</li>
<li>Background workers</li>
</ul>
</li>
<li>Graphics creation (canvas)</li>
<li>A brief mention of WebGL (we also showed the video of Google’s web based DOOM game)</li>
<li>Best practices
<ul>
<li>When JS is disabled</li>
<li>Feature detection</li>
</ul>
</li>
</ul>
</li>
<li>Regular expressions</li>
<li>Ajax (including data interchange formats, like JSON, other async data transmission techniques, including dynamic script loading & JSONP, usability concerns)</li>
<li>SVG</li>
<li>Server side web development
<ul>
<li>PHP (also covering OOP in PHP extensively)</li>
<li>Database driven websites</li>
<li>State & session management</li>
<li>REST</li>
<li>SOAP</li>
</ul>
</li>
<li>Web application security</li>
</ul>
<p><strong><em>Note:</em></strong> <em>For brevity reasons, the lists above do not include introductory stuff such as:</em></p>
<ul>
<li><em>What’s X?</em></li>
<li><em>A brief history of X</em></li>
<li><em>Why use X?</em></li>
<li><em>etc</em></li>
</ul>
<h4 id="lessons-learned" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#lessons-learned">Lessons learned</a></h4>
<p>It’s very hard to momentarily change your mindset and try to imagine that you live in a modern, fully standards-based web development world, where old browsers, proprietary stuff, hacks and compatibility workarounds have no place. A world where IE doesn’t exist. However, it’s the world that all our material assumed, for the reasons stated above. And it’s beautiful, so much that it becomes addictive and makes you hate all these bugs & incompatibilities that we have to face today even more.</p>
<h3 id="homework" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#homework">Homework</a></h3>
<p>The students were given 3 assignments throughout the semester, each covering:</p>
<ul>
<li>1st assignment: HTML, CSS, XPath, XSLT</li>
<li>2nd assignment: JavaScript, Ajax, SVG</li>
<li>3rd assignment: Server side web dev + CSS, JavaScript, Ajax</li>
</ul>
<p>These homeworks accounted for 30% of their final grade (10% each), which probably should have been more.</p>
<p>We searched for exercises on these topics from other universities but couldn’t find anything, so we made our own. I’ve translated them, in case someone finds them useful, given that there’s a great shortage of such material in the intertubes. You can get them through the links below, along with their complementary files.</p>
<h4 id="1st-assignment-%5Bpdf%5D-%5Bfiles%5D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#1st-assignment-%5Bpdf%5D-%5Bfiles%5D">1st assignment [<a href="http://lea.verou.me/wdclass/assignment1_en.pdf">pdf</a>] [<a href="http://lea.verou.me/wdclass/assignment1_en.zip">files</a>]</a></h4>
<ul>
<li>I think 1.A and 1.B are excellent exercises to make the students fully understand how CSS selectors work and avoid them resulting to only use the 4-5 basic ones just because they don’t understand the rest (like many web developers do). It’s a pity that many of them resulted to online scripts for the conversion (but luckily it was easy to spot: These answers were way more verbose than the corresponding “handmade” ones, and in some cases even incorrect!)</li>
<li>I also think 1.C is an excellent exercise for cascading & inheritance practice. Some of the cases were even quite tricky (for instance, the way specificity works for :not() or how grouping works if one of the selectors is invalid) and treated almost all factors that someone should know to predict which rule …overrules. It’s important however that the student justifies the answer, because otherwise they can just test it in a browser and write down the result, without understanding why.</li>
<li>I’m not sure yet if freeform questions were a good idea, but (hopefully) they got them to practice their critical thinking and do some research (we hadn’t presented :checked and :lang() in class). We didn’t expect many to get the 3rd one right, but we were pleasantly surprised.</li>
<li>What I like in 3.A is that I believe it enforces the Separation of Concerns guideline, since they cannot alter the HTML file (something even professionals commonly do to get something done, the quick & dirty way…) so they <strong>have</strong> to move all presentation to the CSS file. It also contained a quite tricky part: Maintaining state without JavaScript, by utilizing the <strong>:checked</strong> pseudo-class and some combinators (a technique made popular quite recently by <a href="http://www.thecssninja.com/">Ryan Seddon</a>). Obviously, this is not a good way to change views in a photo gallery (too much wasted bandwidth), but it was perfect as a CSS exercise. To my surprise, more than half of the students got it right, which indicates that we probably did a good job explaining CSS Selectors :)</li>
</ul>
<h4 id="2nd-assignment-%5Bpdf%5D-%5Bfiles%5D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#2nd-assignment-%5Bpdf%5D-%5Bfiles%5D">2nd assignment [<a href="http://lea.verou.me/wdclass/assignment2_en.pdf">pdf</a>] [<a href="http://lea.verou.me/wdclass/assignment2_en.zip">files</a>]</a></h4>
<ul>
<li>I like exercise 1 because it teaches them how they can take somebody else’s work, extend it and make it more generic and useful. This is something that’s frequently done in web development. By the way, the deviation in the solutions was quite interesting. Others had implemented a recursive algorithm, others approached it in an Object Oriented manner and others took the classic iterative route.</li>
<li>Exercise 2 lets them practice event delegation, unobtrusive progressive enhancement via JavaScript, decisions to improve performance (and still, it’s unbelievable how many students made choices that were obviously terrible performance-wise. I still remember one script that created another DOM element <strong>on every mouseover</strong>!)</li>
<li>Exercise 3 combines many of the technologies they learned in the previous lectures. It also lets them practice their critical thinking by comparing the methods afterwards. Most students picked the CSS method, which would also be my choice, for such a simple bar chart (however, anything rational got full points, I don’t think there’s a correct answer here, it depends on many factors).</li>
<li>I like exercise 4 because it introduces them to the concept of writing JavaScript that is intended to be used by other developers, and not just in a particular project (along with 2 perhaps). However, none of the students fully understood what it was about. All of them fired the HTTP request when ajaxForm() was called and most of them also implemented callback() and errorCallback(), which wasn’t supposed to be their job.</li>
<li>Exercise 5, besides serving well as regular JavaScript practice, it also lets them learn more about cutting edge technologies such as <strong>localStorage</strong>, <strong>Web databases</strong> or <strong>offline web apps</strong>.</li>
</ul>
<h4 id="3rd-assignment-%5Bpdf%5D-%5Bfiles%5D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#3rd-assignment-%5Bpdf%5D-%5Bfiles%5D">3rd assignment [<a href="http://lea.verou.me/wdclass/assignment3_en.pdf">pdf</a>] [<a href="http://lea.verou.me/wdclass/assignment3_en.zip">files</a>]</a></h4>
<p>In this assignment, the students practiced in PHP, combined everything else they’ve learned and understood better how everything clicks together to bring a fully-fledged web application to life. We didn’t get many submissions, since most students were busy with other assignments these days but most of the ones we got were awesome, I had an extremely hard time picking the best one.</p>
<h4 id="lessons-learned-1" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#lessons-learned-1">Lessons learned</a></h4>
<ul>
<li>Most mistakes are not very original: They tend to appear over and over again in unrelated assignments. Most of them are caused either by ambiguities in the description or because the student didn’t bother to read all of it. Also, the most frequent excuse for not doing something right is “it wasn’t in the description!”. So, they have to be as detailed as possible, including even stuff that’s obvious to someone more experienced.</li>
<li>Plagiarism is not a myth, but a real and frequent problem. Students copy from other students, from scripts posted online and from any source they can get their hands on. :( However, only teaching the standards makes it much easier to spot (at least when it comes to copying from the internet) since most scripts posted online have to account for browser incompatibilities.</li>
</ul>
<h3 id="labs" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#labs">Labs</a></h3>
<p>We only held 3 hands-on lectures (2 hours each), due to time availability issues of everyone involved in the course. I taught the first 2 and another TA was responsible for the 3rd one. Details below:</p>
<h4 id="1st-lab-%5Bfinal-result%5D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#1st-lab-%5Bfinal-result%5D">1st lab [<a href="http://lea.verou.me/wdclass/lab1/">final result</a>]</a></h4>
<p>The students had to write an HTML file for the single page personal website of some fictional web developer and then use CSS to style it in a certain way. The process was guided, in order to keep all of them on the same track. The site was carefully designed to demonstrate many key CSS concepts & features at once.</p>
<h4 id="2nd-lab-%5Bfinal-result%5D-%5Bjs-code%5D-%5Bincomplete-js-code%5D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#2nd-lab-%5Bfinal-result%5D-%5Bjs-code%5D-%5Bincomplete-js-code%5D">2nd lab [<a href="http://lea.verou.me/wdclass/rating-widget/">final result</a>] [<a href="http://lea.verou.me/wdclass/rating-widget/rating-widget.js">JS code</a>] [<a href="http://lea.verou.me/wdclass/rating-widget/rating-widget-incomplete.js">incomplete JS code</a>]</a></h4>
<p>The students were given an HTML and a CSS file and they had to fill in a .js file that had some parts missing (replaced by TODO comments as placeholders) to complete a very simple ajax rating widget.</p>
<h4 id="lessons-learned-2" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#lessons-learned-2">Lessons learned</a></h4>
<ul>
<li>Never provide downloadable slides with the things the students must write by themselves prior to the lecture. They’ll just copy-paste everything from the pdf, even if they have to fix spacing afterwards. If you absolutely have to, make sure the text is not selectable.</li>
<li>It takes students far more time to write code than you planned for</li>
<li>When the students don’t understand something, most of them won’t ask. :( It’s best if you personally explain things to anyone having difficulties, but there’s usually not enough time for that</li>
</ul>
<h3 id="personal-aftermath" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/#personal-aftermath">Personal aftermath</a></h3>
<ul>
<li>I found out that I <strong>love</strong> teaching. Successfully helping a student with a problem they had or something they did not understand was sometimes enough to make my day. Preparing material for the course --although exhausting-- was one of the most interesting and creative things I have ever done. Even the actual teaching is thrilling. It’s very challenging to try to keep the students’ interest, since most of them will resort to chatting with their buddies instead of paying attention way more easily than professionals would during a conference talk. However, if you manage to do so, it can be quite rewarding.</li>
<li>I <strong>hate</strong> grading. It’s boring, time-consuming, carries a lot of responsibility and you have to ensure every point you deduct is justified, because you might have to defend your judgement in case a student complains. Sometimes it can also freak you out completely (“OMGWTF, how could they understand it so wrong?? Why didn’t they ask?”) These strips sum it up perfectly (and with a good dose of humor):</li>
</ul>
<p><a href="http://www.phdcomics.com/comics.php?f=1319"><img src="http://www.phdcomics.com/comics/archive/phd051910s.gif" alt="Grading Rubric" title="Grading Rubric" /></a></p>
<p><a href="http://www.phdcomics.com/comics.php?f=1320"><img src="http://www.phdcomics.com/comics/archive/phd052110s.gif" alt="If only" title="If only" /></a></p>
Lea Verou @ Front-Trends 20102010-08-03T00:00:00Zhttps://lea.verou.me/?p=627<p>Just a quick note to let you know that <a href="http://front-trends.com/speakers#lea-verou">I’m speaking</a> in this year’s <a href="http://front-trends.com/">Front-Trends</a> conference, which will take place in Warsaw, Poland on October 21-22. Front-Trends is a new conference (starting this year) but the organizers have managed to put together an impressive line-up (<a href="http://front-trends.com/speakers#douglas-crockford">Crockford</a>, <a href="http://front-trends.com/speakers#peter-paul-koch">PPK</a>, <a href="http://front-trends.com/speakers#paul-bakaus">Paul Bakaus</a>, <a href="http://front-trends.com/speakers#dmitry-baranovskiy">Dmitry Baranovskiy</a>, <a href="http://front-trends.com/speakers#tantek-celik">Tantek Çelik</a>, <a href="http://front-trends.com/speakers#robert-nyman">Robert Nyman</a> and more).</p>
<p>My talk will introduce many aspects of CSS3, some of them in good depth (eg. selectors). Here is the official abstract:</p>
<h4 id="pragmatic-css3" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/08/lea-verou-at-front-trends-2010/#pragmatic-css3">Pragmatic CSS3</a></h4>
<p>With browsers constantly adding support for CSS3, especially now that even IE jumped in the game, it’s quickly becoming a necessary tool of the trade. CSS3 offers exciting possibilities and changes the way that we design and develop websites.</p>
<p>In this 2-hour practical session, full of real world use cases, you will learn:</p>
<ul>
<li>Everything you ever wanted to know about CSS3 selectors</li>
<li>Transparency and new color formats, including RGBA</li>
<li>New ways to work with backgrounds, including CSS gradients, multiple background images and natively supported CSS sprites</li>
<li>Rounded corners and border images</li>
<li>Box and text shadows</li>
<li>Transforms, transitions and their potential downsides</li>
<li>New values, including calc(), attr() and new units</li>
<li>Browser support information and techniques to take advantage of the exciting new stuff with respect to browsers of the past, to create experiences that are enjoyable for everyone</li>
</ul>
<p>Tickets are very cheap (Just <strong>€198</strong>) but they’re selling quite fast, so if you want to come, <a href="http://front-trends.com/registration">hurry up</a>!</p>
Automatic login via notification emails?2010-08-14T00:00:00Zhttps://lea.verou.me/?p=641<p><img src="https://lea.verou.me/2010/08/automatic-login-via-notification-emails/images/twitter-notification.png" alt="Screenshot of a Twitter email notification" title="Email notification example (via Twitter)" />A couple hours ago, I received a notification email from <a href="http://www.goodreads.com/">Goodreads</a> and unlike usually, I decided to actually visit the site (by the way, I believe that Goodreads, i.e. a <a href="http://last.fm/">last.fm</a> for books, is an awesome idea but poorly implemented).When I did, I was quite annoyed to find out that I wasn’t already logged in, so I had to remember which one of my many passwords I had used for it and try them one by one. This is not a Goodreads fail, but a fairly common nuisance, since most (if not all) social websites behave that way.</p>
<p><em>“What if there was some magic involved?”</em> Bill Scott & Theresa Neil advise interaction designers to ask themselves in <a href="http://designingwebinterfaces.com/">a book I’m currently reading</a> (highly recommended by the way). Well, I guess, if there <em>was</em> some magic involved, <strong>the site would “understand” that my click was initiated from an email and would automatically log me in and let me view whatever I was trying to</strong>.</p>
<p>What’s the point of asking for a password if the user can prove they have access to the associated email account? Such access is usually all that’s needed for someone to break into an account, theirs or not (via the forgotten password feature). So, it doesn’t help security much, just makes it slightly more time-consuming for potential impostors, while turning legitimate users with a weak memory (like yours truly) away from the site.</p>
<p>I’m not sure whether it’s a good or a stupid idea, I’m not really suggesting it, just expressing a thought. :) I have some concerns myself too:</p>
<ol>
<li>It’s definitely <strong>harder to implement</strong>.</li>
<li>All links sent in notification emails must contain some special token, like reset password links do (I’ve never seen it implemented otherwise). The tokens in reset password links expire after a while, so probably these should too, for security reasons. And what happens after that? A regular login is required? Doesn’t this render the whole idea a bit pointless, since notification emails are frequently read 1+ days after they’re sent?</li>
<li>Usually a frequent user receives a bunch of email notifications per day. <strong>Isn’t it a bit too risky to have dozens of such powerful emails floating around in your inbox?</strong> On the other hand, it doesn’t seem more dangerous than using the “remember me” feature while logging in: Anyone that manages to get ahold of your laptop for a minute is able to use your account in most SN sites, one way or another. <strong>However, the “remember me” feature is a classic case where usability triumphed security</strong>, at least in cases where the computer isn’t shared.</li>
<li>Thinking of the “remember me” feature gives me another idea: It could be <strong>optional and active by default</strong>. Perhaps with a link to easily deactivate the feature in every such email. On the other hand, more options = more confusion.</li>
<li>Also, to avoid the issues stated in #3, this feature could be activated <strong>only if the user in question was inactive</strong> for a while. <strong>Frequent users don’t need it</strong> that much and even if they did, they don’t run away so easily, so it’s not as crucial.</li>
</ol>
<p><strong>What do you think? Mostly useful or mostly evil?</strong></p>
On attr() and calc()2010-09-11T00:00:00Zhttps://lea.verou.me/?p=653<p>I recently posted my first suggestion to <a href="http://lists.w3.org/Archives/Public/www-style/">www-style</a>, the official W3 mailing list for CSS development. It was about <a href="http://lists.w3.org/Archives/Public/www-style/2010Sep/0019.html">allowing attr() values inside calc()</a>. In this post I’ll describe in greater detail why I believe this is necessary, since not everyone follows www-style. If anyone has something to add in the discussion, you may post in the list, it’s public.</p>
<h3 id="attr()" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/09/on-attr-and-calc/#attr()">attr()</a></h3>
<p>As you can easily <a href="http://www.w3.org/TR/css3-values/#attribute">find out in the specification</a>, the W3 is planning for attr() to play a much bigger role in tomorrow’s CSS than it played in CSS 2.1, <a href="http://www.w3.org/TR/CSS2/generate.html#propdef-content">where it was originally defined</a>, which opens up exciting possibilities. In a nutshell, we’re going to be able to use <code>attr()</code> in any property, for any type of value, let it be <code><length></code>, <code><number></code>, <code><color></code> or anything else. If the type is not obvious, we’re able to define it, via the second parameter and include a fallback value in the 3rd one. We might even be able to do things like <code>float: attr(X</code>); (keywords are still under consideration).</p>
<h3 id="calc()" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/09/on-attr-and-calc/#calc()">calc()</a></h3>
<p>On the other hand, as you’re probably already aware of, since <a href="http://www.w3.org/TR/css3-values/#calc">calc()</a> is one of the hyped CSS3 features, we’re finally going to be able to do calculations with different types of units, for example <code>calc(100% - 30px)</code>, which is something web designers requested for years.</p>
<h3 id="calc(attr())" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/09/on-attr-and-calc/#calc(attr())">calc(attr())</a></h3>
<p>You can easily see from the grammar presented in the specification for calc() that it does not allow attr() values to be used as operands in the calculations. To me, this is an obvious oversight. Since attr() values can be used anywhere, including where lengths and numbers are allowed, not being able to use them in calc() is absurd. As <a href="http://lists.w3.org/Archives/Public/www-style/2010Sep/0072.html">David Storey pointed out</a>, this could be enormously useful when used in conjunction with the new form control attributes (min, max, step and the like) or HTML5 custom data attributes (data-x).</p>
<p>Philosophically, it makes perfect sense that attr() should be allowed anywhere a <code><length></code> or <code><number></code> or <code><angle></code> or … is. <strong>We can’t expect attributes to only hold semantic and not presentational data, but expect these data to be ready to be utilized for presentation purposes, without any calculations whatsoever</strong>.</p>
<p>The first use case I can think of is the one that inspired me to suggest this. A while ago, I was researching CSS-based bar charts and progress bars. It turned out that there is no practical and purely semantic solution for specifying the bar widths. Either you have <a href="http://www.1080degrees.net/archive/journal/simple_css_bar_graph/">to</a> <a href="http://www.alistapart.com/articles/accessibledatavisualization">include</a> <a href="http://icant.co.uk/csscharts/">inline</a> <a href="http://www.standards-schmandards.com/exhibits/barchart/">styles</a> or you bloat your CSS <a href="http://meyerweb.com/eric/css/edge/bargraph/demo.html">with</a> <a href="http://www.usrecordings.com/test-lab/bullet-graph.htm">countless</a> <a href="http://cssglobe.com/post/1272/pure-css-data-chart">classes</a> <a href="http://www.cssplay.co.uk/menu/barchart.html">or</a> <a href="http://csswizardry.com/2010/02/css-bar-charts-styling-data-with-css3-and-progressive-enhancement/">ids</a>, one for each width or —even worse— bar. In cases where you just want to use the displayed percentage of the bar as its width as well, attr() can actually help. However, as you can see, this is not always the case. Most of the times the bar values are not percentages or you want to also perform calculations on the percentage, for example include padding (because usually you display the number as well) or cut it in half to prevent the bar chart from appearing very big, etc, in which calc() combined with attr() could be a lifesaver.</p>
<p>One could argue that bar charts and progress bars are not legitimate CSS use cases but hacks that work around the lack of cross-browser SVG support, and it’s very possible that they are right (although the addition of elements like <a href="http://www.w3schools.com/html5/tag_progress.asp"><code><progress></code></a> in HTML5 is by itself an argument for the opposite). However, the use cases are not limited to that. Αny kind of stylistic treatment that is supposed to convey some kind of fraction or number (progress, temperature, distance etc) will benefit from keeping the actual data in a data-x attribute and utilize them via attr() and calc().</p>
<p>Admittedly, coming up with more generic use cases is not very easy, since they greatly depend on the particular application. However, the same difficulty arises when trying to come up with use cases for the attr() function by itself when used for the numerical types (<code><number></code>, <code><length></code> etc), in properties other than content. Perhaps this is the reason that not even the specification contains any practical examples for it either. I guess almost any real-life use case for attr(*, number|integer|length|angle|frequency|em|px|…, *) is also a use case for this.</p>
<p>So far I’m optimistic about it, since almost all participants in the discussion were positive. However, calc() has already started being implemented (by Mozilla), so as time goes by, it will be increasingly harder to make changes to its grammar.</p>
<p>What do you think? How would you use it if it’s implemented?</p>
<p><strong>Edit:</strong> Sometime in Spring 2012, the issue was brought up again, and the CSS WG agreed that <code>attr()</code> should be permitted in <code>calc()</code>. Now it’s just a matter of browsers catching up to the spec. :)</p>
My FT2010 slides and CSSS: My presentation framework2010-10-29T00:00:00Zhttps://lea.verou.me/?p=676<p><a href="https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/images/cover-screenshot.png"><img src="https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/images/cover-screenshot.png" alt="Screenshot of the first slide" title="cover-screenshot" /></a>About a week ago, I was in Warsaw, Poland to give <a href="http://lea.verou.me/2010/08/lea-verou-at-front-trends-2010/">my first talk</a> at a big conference, <a href="http://front-trends.com/">Front Trends 2010</a>. As every first-time speaker, I was extremely nervous and worried that everything would go bad. That my talk would be boring or too basic or that I would just freeze at stage, unable to say a word. It was a 2-hour talk with a break in between, so I was also terrified that nobody would show up the second hour.</p>
<p>Contrary to my fears and insecurities, it went better than I could have ever hoped. The feedback on twitter and in general was enthusiastic! There wasn’t a single negative comment. Even people I look up to, like <a href="http://twitter.com/#!/t/status/28020396001">Tantek Çelik</a>, <a href="http://twitter.com/ppk/status/28018480426">PPK</a>, <a href="http://twitter.com/jaffathecake/status/28016064374">Jake Archibald</a> or <a href="http://twitter.com/robertnyman/status/28016285089">Robert Nyman</a> had something good to say! And instead of nobody showing up the second hour, the audience almost doubled!</p>
<p>At this point, I would like to thank <a href="http://www.wait-till-i.com/2010/10/25/fronttrends2010-in-warsaw-poland-my-impressions-slides-and-audio/">Christian Heilmann</a> for helping me become less nervous before my talk by going through all my slides with me and offering his invaluable advice for every part (I forgot to follow most of it, but it really helped in my attitude). I can’t thank you enough Christian!</p>
<p>Many attendees asked me for my slides and presentation framework. You can find my slides <a href="http://talks.verou.me/ft2010/">online here</a> or <a href="https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/uploads/ft2010.zip">download</a> them. However, <strong>before you follow those links, read below</strong>:</p>
<ul>
<li>I originally ran my presentation in <strong>Firefox 4 beta</strong> so I was testing mainly in that and Minefield (Firefox’s nightly releases). It supports other browsers too (Chrome 7, Opera 10.6+), but it still displays better in Firefox or Minefield and is (surprisingly) faster in them.</li>
<li><strong>Opera</strong> has issues with a few unicode characters I used in some places and won’t display Helvetica Neue even if it’s installed (@font-face is not an option with that font, for legal reasons)</li>
<li><strong>Any non-Gecko browser</strong> will not display <strong>CSS gradients</strong>, since Gecko is the only engine so far that supports the standard syntax. Therefore the gradient demos and the multiple backgrounds demo won’t work in non-Gecko browsers.</li>
<li>Some slides are a bit slow on <strong>Webkit</strong>. <strong>The first slide is extremely slow in it,</strong> you have been warned.</li>
<li><strong>Opera</strong> and <strong>Webkit</strong> have (different) bugs with border-radius: 50%, so some things using it will look funny.</li>
<li>I have <strong>only tested in OSX browsers</strong>. I have no idea how it will perform on Windows or Linux distros yet.</li>
<li>It’s a 2-hour talk and the presentation was designed to run locally. <strong>It’s not small and it will take a while to load</strong>. That’s due to the images used, as you can easily see from the zip archive.</li>
<li>The <strong>editable examples</strong> many of you liked are based on this <a href="http://annevankesteren.nl/2010/03/css-wg-brainstorming">CSS mindfuck by Anne van Kesteren</a>. It’s smart and convenient, but beware: It breaks really, really easily. It’s good for <em>changing</em> the code realtime, but it will most likely break if you try to <em>add</em> extra code.</li>
</ul>
<p>In case you’re not feeling very adventurous today, or you’re just using a computer with only unsupported browsers, here’s the presentation as a series of images (not interactive, but still the same info):</p>
<p><strong><a href="http://www.slideshare.net/LeaVerou/css3-a-practical-introduction-ft2010-talk" title="CSS3: A practical introduction (FT2010 talk)">CSS3: A practical introduction (FT2010 talk)</a></strong></p>
<p><a href="https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/images/logo-small.png"><img src="https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/images/logo-small.png" alt="CSSS logo" title="CSSS" /></a>By popular demand, I’m also releasing my presentation framework, for which in the meantime I found a name (<strong>CSSS</strong>, inspired by S5), designed a logo and made a simpler, <a href="http://lea.verou.me/csss/sample-slideshow.html">sample presentation</a> with a different, simpler theme. I released it in <a href="http://github.com/LeaVerou/CSSS">a public repo on Github</a> (finally got around to learning the basics of Github and loved it!). Please note that this is a very first version and I haven’t been able to test it much, especially on Windows, since my Mac is quite new and I keep postponing to install some virtualization software. A friend reported that <strong>Firefox 3.6 on Windows has serious issues</strong> with it, although it runs fine on my FF3.6 copy for Mac. It doesn’t work at all in IE, even IE9, as I don’t yet have IE to test it out. Please report any issues on Github’s bug tracker and eventually I --or someone else, you’re all welcome :p-- will fix them (don’t forget to mention exact browser version and OS). If you’re using Safari, press Ctrl+H for something cool ;) (it works on the others too, but it’s slower and not smooth)</p>
<p><a href="https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/images/csss-logo.jpg"><img src="https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/images/csss-logo-300x187.jpg" alt="" title="csss-logo" /></a>Some may ask: <em>“If CSS3 degrades so gracefully and we can use it today as you told us in your talk, then why all these issues with different browsers in CSSS or your FT presentation?”</em>. First of all, these are not everyday use cases. Projects like CSSS or my FT presentation are quite experimental, use a lot of CSS3, including many edge cases and I could have devoted more time to make them degrade more gracefully, but given the target audience, I don’t think it’s worth it much. It’s expected that there might be rendering problems in some browsers or that they might be slow, browsers need edge cases to highlight problems in their implementations of the new stuff before it’s finalized. Every time I experiment with CSS3, I find at least one browser bug, which I generally try to report (don’t let that scare you though, as I said, I have a penchant for edge cases).</p>
<p>You may have also noticed I redesigned my blog. As you may have noticed, I have fallen in love with that <a href="http://lukeroberts.us/2008/12/wallpaper-colourful-wood/">Rainbow Wood wallpaper by Luke Roberts</a> and I just <strong><em>had</em></strong> to put it in my blog too :P The new design has a few issues with Opera at the moment, but I hope to fix them soon. It will also look better to those that have Helvetica Neue installed.</p>
The curious case of border-radius:50%2010-10-30T00:00:00Zhttps://lea.verou.me/?p=705<p>Admittedly, percentages in border-radius are not one of the most common use cases. Some even consider them an edge case, since most people seem to set border-radius in pixels or --rarely-- ems. And since it’s not used very frequently, it’s still quite buggy. A bit of a chicken and egg case actually: Is it buggy because it’s used rarely or is it used rarely because it’s buggy? My vote would go to the first, so the purpose of this post is to let people know about why percentages in border-radius are incredibly useful and to highlight the various browser whims when it comes to rendering them.</p>
<h2 id="specification" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/#specification">Specification</a></h2>
<p>Before we go into discussing implementations, let’s first examine what the right thing to do is, i.e. what <a href="http://www.w3.org/TR/2010/WD-css3-background-20100612/#the-border-radius">the specification says</a>:</p>
<blockquote>
<p>Percentages: Refer to corresponding dimension of the border box.</p>
</blockquote>
<blockquote>
<p>The two length <strong>or percentage</strong> values of the ‘border-*-radius’ properties define the radii of a quarter ellipse that defines the shape of the corner of the outer border edge (see the diagram below). The first value is the horizontal radius, the second the vertical radius. If the second value is omitted it is copied from the first. If either length is zero, the corner is square, not rounded. <strong>Percentages for the horizontal radius refer to the width of the border box, whereas percentages for the vertical radius refer to the height of the border box</strong>.</p>
</blockquote>
<h2 id="why-is-that-useful%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/#why-is-that-useful%3F">Why is that useful?</a></h2>
<p>It’s the only way of utilizing border-radius to draw a circle or ellipse, i.e. <strong>a rounded shape without any straight lines whatsoever</strong> (without knowing the dimensions in advance).</p>
<p>As you will see below, Firefox used to have a bug, or actually a different interpretation of the spec, which I think is a quite commonly needed use case, even more than ellipses: It <strong>always drew a regular curve</strong> for the corners (quarter of a circle) <strong>with the maximum possible radii</strong>. This is a <strong>very commonly needed shape</strong> in UI design. If you’re using OSX, you’re seeing it everywhere: the buttons, the scrollbars, even Skype (notice the blue or grey shading around the usernames in a chat). As I’m writing this post, I can see the same shape in the buttons of Wordpress’ admin panel. And as the current spec stands, there’s no way to do that. You have to know the height (or width, if you want a vertical shape) in advance, which even when possible, makes border-radius depend on the value of other attributes (such as line-height) and you have to remember to change it every time you change those, which causes maintenance headaches. And what’s worse is that the Backgrounds & Borders module is almost done, so it’s quite unlikely that this will change anytime soon. :(</p>
<p>As noted in <a href="http://lea.verou.me/2010/10/the-curious-case-of-border-radius50/#comment-1516">this comment by David Baron</a>, that assumption wasn’t exactly correct about Firefox’s old rendering. It just resolved % as relative to width in every case (kinda like percentages in margins) and when the height was smaller than the width, it applied the rules for radii that are too big, which say to reduce it equally. A straightforward deduction is that we do have a standards-compliant way to get the behavior from old versions of Firefox, in every browser: Just specify a very big radius, like 9999px.</p>
<h2 id="different-implementations%2C-different-bugs" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/#different-implementations%2C-different-bugs">Different implementations, different bugs</a></h2>
<figure class="left">
<a href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Firefox4b6.png" target="_blank"><img src="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Firefox4b6.png" alt="" /></a>
<figcaption>Firefox 4 beta 6</figcaption>
</figure>
<p>As I mentioned above, <strong>Gecko</strong> up to Firefox version 4 beta 6 always draws a regular curve for the corners with the largest radii applicable, resulting in a shape that is either a perfect circle or a rectangle with a semicircle on top and bottom (if height > width) or right and left (if width > height).</p>
<figure class="right">
<a href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Minefield.png" target="_blank"><img src="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Minefield.png" alt="" /></a>
<figcaption>Minefield (latest Gecko nightlies)</figcaption>
</figure>
<p>In the latest nightlies this bug is fixed, and it follows the spec to the letter. I can’t help but wonder if this was a bug, a misinterpretation of the spec or a deliberate disagreement with it.</p>
<figure class="left">
<a href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Firefox4b6.png" target="_blank"><img src="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Webkit.png" alt="" /></a>
<figcaption>WebKit nightlies</figcaption>
</figure>
<p><strong>Webkit</strong> was late to support percentages in border-radius, but it seems to be the first (it or IE9, I’m not sure) to follow the spec to the letter --concerning corner radii at least-- and renders an ellipse (horizontal radius = width/2, vertical radius = height/2) no matter what. Webkit however seems to be having serious trouble with borders, rendering them with variable width strokes (!).</p>
<figure class="right">
<a href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Opera.png" target="_blank"><img src="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/Opera.png" alt="" /></a>
<figcaption>Opera 11</figcaption>
</figure>
<p><strong>Presto</strong> (Opera) is the weirdest when it comes to rendering a percentage border-radius. I can’t figure out the algorithm it uses to determine the radii of the corners even if it was to save my life, it even changes according to window size in my testcases! Since I’ve been using border-radius:50% regularly, I’ve had the pleasure of observing Opera’s rendering in many different designs and I still can’t find a pattern. It’s particularly funny when rendering the little fuchsia comment bubbles in the homepage of my blog: Every one of them has a different radius, even if they are about the same size. It even got one of them right and rendered it as an ellipse once!</p>
<figure class="left">
<a href="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/IE9.png" target="_blank"><img src="https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/images/IE9.png" alt="" /></a>
<figcaption>Internet Explorer 9</figcaption>
</figure>
<p><strong>Trident (IE9)</strong>, along with the latest Gecko nightly is the only 100% correct one when it comes to rendering the testcases, which is not surprising since the IE team boasted quite a lot for their bulletproof border-radius implementation. Well, their CSS3 support might be a bit lacking, but at least the bits they actually implement aren’t buggy. Kudos for that.</p>
<p><a href="http://lea.verou.me/demos/border-radius-50p.html">Link to testcases</a></p>
<p><strong>Note:</strong> Of course all bugs mentioned above have been reported to the respective browser vendors (except the Gecko one that is already fixed in the nightlies).</p>
Tag editing UIs2010-11-14T00:00:00Zhttps://lea.verou.me/?p=745<p>I had to build the edit tags interface for an application I’m working on, so I took a good look at how these are implemented across many popular applications nowadays. It seems there are a few patterns that are used over and over, and I’m unsure which one is the most preferable by users, they all have their advantages and disadvantages. In this post I’m going to describe these patterns and list some of the pros and cons I think they have. For simplicity, I will focus on the tag editing interface itself, ignoring any tag suggestions and other extra features.</p>
<h3 id="pattern-%231%3A-input-field-to-add-new-tags%2C-delete-button-for-existing-ones" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/11/tag-editing-uis/#pattern-%231%3A-input-field-to-add-new-tags%2C-delete-button-for-existing-ones">Pattern #1: Input field to add new tags, delete button for existing ones</a></h3>
<p>Used by: Wordpress, flickr, foursquare<img src="https://lea.verou.me/2010/11/tag-editing-uis/images/Screen-shot-2010-11-14-at-16.45.11-.png" alt="Screenshot of Wordpress' tagging UI" title="Wordpress' tagging UI" /></p>
<p>Pros:</p>
<ul>
<li>One click deletion of tags</li>
</ul>
<p>Cons:</p>
<ul>
<li>Impossible to edit a tag, you have to remove it and add the corrected version</li>
<li>Hard to delete many tags at once</li>
<li>Disconnected new and existing tags, making it hard to get the bigger picture</li>
</ul>
<p>foursquare’s implementation was the worst I’ve tested: There’s no (discoverable?) way to delete or edit a tag and when you add one via the text field it doesn’t get cleared which is confusing because it makes it seem like an edit tags field although it’s an add tags field, as I found out the hard way (by creating a “pizza, pasta” tag instead of 2 tags: pizza and pasta).</p>
<h3 id="pattern-%232%3A-one-text-field-to-edit%2C-delete-or-add-new-tags" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/11/tag-editing-uis/#pattern-%232%3A-one-text-field-to-edit%2C-delete-or-add-new-tags">Pattern #2: One text field to edit, delete or add new tags</a></h3>
<p>Used by: delicious, Google reader, stackoverflow, reddit<a href="https://lea.verou.me/2010/11/tag-editing-uis/images/Screen-shot-2010-11-14-at-16.47.04-.png"><img src="https://lea.verou.me/2010/11/tag-editing-uis/images/Screen-shot-2010-11-14-at-16.47.04-.png" alt="Screenshot of delicious' tagging UI" title="Delicious' tagging UI" /></a></p>
<p>Pros:</p>
<ul>
<li>Lets the user edit tags too, in addition to adding and deleting</li>
<li>Easy to delete many tags at once</li>
<li>All tags at one place</li>
</ul>
<p>Cons:</p>
<ul>
<li>More cumbersome to delete a tag</li>
<li>A bit more prone to mistakes than guided interfaces</li>
</ul>
<h3 id="pattern-%233%3A-hybrid-approach%3A-text-field-for-all%2C-existing-tags-seem-to-be-inside-and-have-a-delete-button" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/11/tag-editing-uis/#pattern-%233%3A-hybrid-approach%3A-text-field-for-all%2C-existing-tags-seem-to-be-inside-and-have-a-delete-button">Pattern #3: Hybrid approach: Text field for all, existing tags seem to be inside and have a delete button</a></h3>
<p>Used by: <a href="http://last.fm/">last.fm</a><img src="https://lea.verou.me/2010/11/tag-editing-uis/images/Screen-shot-2010-11-14-at-16.54.45-.png" alt="Screenshot of last.fm's tagging UI" title="last.fm's tagging UI" /></p>
<p>Pros:</p>
<ul>
<li>All tags in one place</li>
<li>One click deletion</li>
<li>Easy to delete many tags too</li>
</ul>
<p>Cons:</p>
<ul>
<li>There’s no editing in <a href="http://last.fm/">last.fm</a>’s implementation, but the pattern easily allows for that, for example by using contentEditable on the tag <a>s</a></li>
</ul>
<p><a href="http://last.fm/">last.fm</a> chooses to implement this by faking the tags being inside an input field: Technically they’re implemented just like in pattern #1 above, with the difference that they visually appear to be inside the same box and every time a user inserts a comma (which is the tag separator) the tag they just typed is removed from the text field and a new <a> link with a delete button is created just before the text field, which is much smaller than it looks.</a></p>
<h3 id="which-pattern-is-the-best%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/11/tag-editing-uis/#which-pattern-is-the-best%3F">Which pattern is the best?</a></h3>
<p>As with most UI questions, I don’t think there’s a definite answer to that. It heavily depends on the audience too: A more technically inclined user might be more comfortable with the 2nd approach since it’s the least restrictive one. The average casual internet user might prefer the 3rd approach. I don’t think there’s any case where pattern #1 is better than pattern #3, except when development time is a concern (pattern #1 is a bit easier to implement, although #2 is the easiest of all).</p>
<h3 id="another-pattern%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/11/tag-editing-uis/#another-pattern%3F">Another pattern?</a></h3>
<p>My initial attempt for the application I’m building was to use a hybrid approach of #2 and #3: When the user clicked on “Edit tags”, the tag container would get a <code>contentEditable</code> attribute and the idea was that every time a comma or any other non-permitted character would be inserted a new tag would be created (or if we were in the middle of one, it would get split into 2). That would have all the advantages of #2 and #3, except one-click deletion. It would also have the advantage that the user is directly editing the interface, which is usually a good idea usability-wise. I hate to admit I gave up on it for the time being, because it proved harder to implement than it seemed and I had to move on, so I went with #2. I might revisit it sometime in the future though if I still think it’s a good idea and nobody has done so by then.</p>
rgba.php v1.2: Improved URL syntax, now at Github2010-12-08T00:00:00Zhttps://lea.verou.me/?p=763<p><a href="https://lea.verou.me/2010/12/rgba-php-v1-2-improved-url-syntax-now-at-github/images/Screen-shot-2011-11-15-at-15.19.14-.png"><img src="https://lea.verou.me/2010/12/rgba-php-v1-2-improved-url-syntax-now-at-github/images/Screen-shot-2011-11-15-at-15.19.14--300x187.png" alt="" title="rgba.php project page screenshot" /></a>I wrote the first version of rgba.php as a complement to <a href="http://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/">an article on RGBA that I posted on Februrary 2009</a>. Many people seemed to like the idea and started using it. With their valuable input, I made many changes and <a href="http://lea.verou.me/2009/10/new-version-of-rgba-php-is-out/">released v.1.1</a> (1.1.1 shortly after I posted the article due to another little fix) on October 2009. More than a year after, quite a lot of people still ask me about it and use it, so I decided to make <a href="https://github.com/LeaVerou/rgba.php">a github repo for it</a> and release a new version, with a much easier to use syntax for the URL, which lets you just copy and paste the color instead of rewriting it:</p>
<p>background: url(‘rgba.php/rgba(255, 255, 255, 0.3)’);
background: rgba(255, 255, 255, 0.3);</p>
<p>instead of:</p>
<p>background: url(‘rgba.php?r=255&g=255&b=255&a=30’);
background: rgba(255, 255, 255, 0.3);</p>
<p>I also made <a href="http://lea.verou.me/rgba.php/">a quick about/demo page for it</a>. Enjoy :)</p>
Checkerboard, striped & other background patterns with CSS3 gradients2010-12-22T00:00:00Zhttps://lea.verou.me/?p=775<p><a href="http://lea.verou.me/demos/css3-patterns.html"><img src="https://lea.verou.me/2010/12/checkered-stripes-other-background-patterns-with-css3-gradients/images/css3-patterns-1024x480.png" alt="Screenshot of the CSS3 patterns I came up with" title="The CSS3 patterns I came up with" /></a>You’re probably familiar with CSS3 gradients by now, including the closer to the standard Mozilla syntax and the ugly verbose Webkit one. I assume you know how to add multiple color stops, make your gradients angled or create radial gradients. What you might not be aware of, is that CSS3 gradients can be used to create many kinds of commonly needed patterns, including checkered patterns, stripes and more.</p>
<p><a href="https://lea.verou.me/demos/css3-patterns.html" class="call-to-action">View demo</a> (Works in Webkit, Firefox 3.6+, Opera 11.50+ and IE10+)</p>
<p>The main idea behind the technique is the following, taken from the <a href="http://dev.w3.org/csswg/css3-images/#color-stop-syntax">CSS3 Images spec</a>:</p>
<blockquote>
<p>If multiple color-stops have the same position, they produce an infinitesimal transition from the one specified first in the rule to the one specified last. <strong>In effect, the color suddenly changes at that position rather than smoothly transitioning.</strong></p>
</blockquote>
<p>I guess this makes it obvious how to create the tile for the stripes (unless you’ve never created a striped background before, but teaching you this is beyond the scope of this post). For example the gradient for the horizontal stripes is:</p>
<p>background-color: #0ae;
background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(.5, rgba(255, 255, 255, .2)), color-stop(.5, transparent), to(transparent));
background-image: -moz-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);
background-image: -o-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);
background-image: linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);</p>
<p>Why <code>transparent</code> instead of the actual colors we want? For flexibility. <code>background-color</code> serves two purposes here: Setting the color of half the stripes and serving as a fallback for browsers that don’t support gradients.</p>
<p>However, without anything else, the tile will occupy the whole container. To control the size of each tile, you can use background-size:</p>
<p>-webkit-background-size: 50px 50px;
-moz-background-size: 50px 50px;
background-size: 50px 50px;</p>
<p>To create the picnic-style pattern, you just overlay horizontal stripes on vertical stripes.</p>
<p>The hardest one to figure out was the checkered pattern. It consists of two 45° linear gradients and two -45° linear gradients, each containing ¼ of the dark squares. I still haven’t managed to think of a way to create a regular checkerboard (not at 45°) without needing an unacceptably large number of gradients. It will be very easily possible if conical gradients start being supported (currently they’re not even in the spec yet).</p>
<p>Can you think of any other popular patterns that can be created with CSS3 and no images? If so, let me know with a comment. Cheers! :)</p>
<h3 id="added-afterwards%3A-other-patterns" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2010/12/checkered-stripes-other-background-patterns-with-css3-gradients/#added-afterwards%3A-other-patterns">Added afterwards: Other patterns</a></h3>
<p>There are far more pattern designs possible with CSS3 gradients than I originally thought. For more details, see <a href="http://lea.verou.me/2011/04/css3-patterns-gallery-and-a-new-pattern/">this later post</a>.</p>
Yet another redesign2011-01-05T00:00:00Zhttps://lea.verou.me/?p=799<p>I had grown sick of my previous blog style and its various bugs (since it was put together in just a few hours), so I decided to make a new, more minimalistic one. Best viewed in browsers that support CSS gradients, like Firefox, Safari and Chrome. I also finally got around to making a logo for myself, although I’m not sure I’ll keep it. I also switched to HTML5, using <a href="http://wordpress.org/extend/themes/toolbox">Toolbox</a> as a base.</p>
<p>I want to make a few more changes, but I have to go to sleep sometime :p</p>
<p>I also started using <a href="http://disqus.com/">DISQUS</a> for the blog comments. I like it when a blog I read has it (since it offers a few features I find convenient, like comment editing for instance), so I wanted to offer it to my readers too. It’s a shame that in some of their buttons they haven’t added the standard CSS3 border-radius declarations, but only the prefixed proprietary ones, so they’re square in Opera (and probably IE9). I’m fed up with seeing this in websites, <a href="http://topsy.com/">TOPSY</a>’s widget also does it. However, their carelessness will backfire soon, when browsers stop supporting the prefixed versions *evil grin*</p>
I'm speaking at @media Web Directions ’11!2011-01-05T00:00:00Zhttps://lea.verou.me/?p=809<p>Just a quick note to let you know I’m speaking at <a href="http://atmedia11.webdirections.org/">this year’s @media Web Directions</a> conference, which will take place during May 26–27 in London, UK. I’m very excited about this, since I always considered @media one of the top front-end conferences in the industry :)</p>
<p>The title and abstract of <a href="http://atmedia11.webdirections.org/program/design/#css3-at-the-outer-rim">my talk</a> is as follows:</p>
<blockquote>
<h3 id="css3-at-the-outer-rim" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/01/im-speaking-at-atmedia-web-directions-11/#css3-at-the-outer-rim">CSS3 at the Outer Rim</a></h3>
<p>By now most of you know how to use the core CSS3 features in your designs to embed custom fonts and easily create rounded corners, drop shadows, and scalable designs with media queries. But there is still a large area of CSS3 that remains unexplored by most web designers and developers. In this talk Lea will present many CSS3 features that are useful but underrated, as well as uncommon ways of utilising the CSS3 features you already know about, in order to do much more with even fewer images and less code.</p>
</blockquote>
<p>Although it’s on the design track, I expect it to appeal to both developers and designers.</p>
<p>You can use the coupon code <strong>WDVEROU</strong> to take £50 off the current price. ;)</p>
<p>Hope to see you there! :D</p>
Styling elements based on sibling count2011-01-17T00:00:00Zhttps://lea.verou.me/?p=825<p>The <a href="http://andr3.net/blog/post/142">original idea belongs to André Luís</a>, but I think it could be improved to be much less verbose.</p>
<p>André’s solution is like this:</p>
<pre><code>/* one item */
li:nth-child(1):nth-last-child(1) {
width: 100%;
}
/* two items */
li:nth-child(1):nth-last-child(2),
li:nth-child(2):nth-last-child(1) {
width: 50%;
}
/* three items */
li:nth-child(1):nth-last-child(3),
li:nth-child(2):nth-last-child(2),
li:nth-child(3):nth-last-child(1) {
width: 33.3333%;
}
/* four items */
li:nth-child(1):nth-last-child(4),
li:nth-child(2):nth-last-child(3),
li:nth-child(3):nth-last-child(2),
li:nth-child(4):nth-last-child(1) {
width: 25%;
}
</code></pre>
<p>It’s based on the relationship between :nth-child and :nth-last-child. As you can see, the number of total rules is O(N) and the number of selectors in every rule is also O(N).</p>
<p>However, what you really want, is to just target the first element. The others can be targeted with just a sibling selector. With my improvement, the number of total rules is still O(N), but the number of selectors in every rule becomes just 2, making this trick practical for far larger numbers of children:</p>
<p>/* one item */
li:first-child:nth-last-child(1) {
width: 100%;
}</p>
<p>/* two items */
li:first-child:nth-last-child(2),
li:first-child:nth-last-child(2) ~ li {
width: 50%;
}</p>
<p>/* three items */
li:first-child:nth-last-child(3),
li:first-child:nth-last-child(3) ~ li {
width: 33.3333%;
}</p>
<p>/* four items */
li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4) ~ li {
width: 25%;
}</p>
<p>And here’s a fiddle to prove it:</p>
<iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/leaverou/HdfaT/embedded/result,css,html"></iframe>
<p>Yes, I know that with Flexbox and the other layout modules, techniques such as these are soon becoming obsolete, but I think they are still useful right now. I’m also aware that you can emulate this particular example with table display modes, but a) Table display modes have other implications that are sometimes undesirable and b) Widths are just an example, you could come up with other ways to style the elements based on their total count, which can’t be emulated by CSS tables.</p>
Convert PHP serialized data to Unicode2011-02-13T00:00:00Zhttps://lea.verou.me/?p=835<p>I recently had to convert a database of a large Greek website from single-byte Greek to Unicode (UTF-8).
One of the problems I faced was the stored PHP serialized data: As PHP stores the length of the data (in bytes) inside the serialized string, the stored serialized strings could not be unserialized after the conversion.</p>
<p>I didn’t want anyone to go through the frustration I went through while searching for a solution, so here is a little function I wrote to recount the string lengths, since I couldn’t find anything on this:</p>
<pre><code class="language-php">function recount_serialized_bytes($text) {
mb_internal_encoding("UTF-8");
mb_regex_encoding("UTF-8");
mb_ereg_search_init($text, 's:[0-9]+:"');
$offset = 0;
while(preg_match('/s:([0-9]+):"/u', $text, $matches, PREG_OFFSET_CAPTURE, $offset) ||
preg_match('/s:([0-9]+):"/u', $text, $matches, PREG_OFFSET_CAPTURE, ++$offset)) {
$number = $matches[1][0];
$pos = $matches[1][1];
$digits = strlen("$number");
$pos_chars = mb_strlen(substr($text, 0, $pos)) + 2 + $digits;
$str = mb_substr($text, $pos_chars, $number);
$new_number = strlen($str);
$new_digits = strlen($new_number);
if($number != $new_number) {
// Change stored number
$text = substr_replace($text, $new_number, $pos, $digits);
$pos += $new_digits - $digits;
}
$offset = $pos + 2 + $new_number;
}
return $text;
}
</code></pre>
<p>My initial approach was to do it with regular expressions, but the PHP serialized data format is not a regular language and cannot be properly parsed with regular expressions. All approaches fail on edge cases, and I had lots of edge cases in my data (I even had nested serialized strings!).</p>
<p>Note that this will only work when converting <strong>from single-byte encoded data</strong>, since it assumes the stored lengths are the string lengths in characters. Admittedly, it’s not my best code, it could be optimized in many ways. It was something I had to write quickly and was only going to be used by me in a one-time conversion process. However, it works smoothly and has been tested with lots of different serialized data. I know that not many people will find it useful, but it’s going to be a lifesaver for the few ones that need it.</p>
Incrementable length values in text fields2011-02-14T00:00:00Zhttps://lea.verou.me/?p=839<p><a href="https://lea.verou.me/2011/02/incrementable-length-values-in-text-fields/images/incrementable-demo.png"><img src="https://lea.verou.me/2011/02/incrementable-length-values-in-text-fields/images/incrementable-demo-300x202.png" alt="" title="incrementable-demo" /></a>I always loved that Firebug and Dragonfly feature that allows you to increment or decrement a <code><length></code> value by pressing the up and down keyboard arrows when the caret is over it. I wished my <a href="http://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/">Front Trends slides</a> supported it in the editable examples, it would make presenting so much easier. So, I decided to implement the functionality, to use it in my next talk.</p>
<p>If you still have no idea what I’m talking about, you can see a demo here: <a href="https://incrementable.verou.me/" class="call-to-action">View demo</a></p>
<p>You may configure it so that it only does that when modifiers (alt, ctrl and/or shift) are used by providing a second argument to the constructor and/or change the units supported by filling in the third argument. However, bear in mind that holding down the Shift key will make it increment by ±10 instead of ±1 and that’s not configurable (it would add too much unneeded complexity, I’m not even sure whether it’s a good idea to make the other thing configurable either).</p>
<p>You may download it or fork it from it’s <a href="https://github.com/LeaVerou/Incrementable/">Github repo</a>.</p>
<p>And if you feel creative, you may improve it by fixing an Opera bug I gave up on: When the down arrow is pressed, the caret moves to the end of the string, despite the code telling it not to.</p>
Checkerboard pattern with CSS32011-02-16T00:00:00Zhttps://lea.verou.me/?p=850<p>A while ago, I wrote a post on <a href="http://lea.verou.me/2010/12/checkered-stripes-other-background-patterns-with-css3-gradients/">creating simple patterns with CSS3 gradients</a>. A common pattern I was unable to create was that of a regular, non-rotated checkerboard. However, I noticed today that by giving a different background-position to every triangle in the pattern tile, a checkerboard can be easily created:</p>
<iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/leaverou/SUgfD/embedded/result,css"></iframe>
<p>View in Gecko or Webkit. Webkit seems to have an odd rendering bug, so it needed a background-size override and it still doesn’t look perfect. Oh well, <a href="https://bugs.webkit.org/show_bug.cgi?id=54805">reported the bug</a> and moved on.</p>
Custom <select> drop downs with CSS32011-03-04T00:00:00Zhttps://lea.verou.me/?p=856<p>The CSS3 Basic UI module defines <a href="http://dev.w3.org/csswg/css3-ui/#pointer-events"><code>pointer-events</code></a> as:</p>
<blockquote>
<p>The <code>pointer-events</code> property allows authors to control whether or when an element may be the target of user pointing device (pointer, e.g. mouse) events. This property is used to specify under which circumstance (if any) a pointer event should go “through” an element and target whatever is “underneath” that element instead. This also applies to other “hit testing” behaviors such as dynamic pseudo-classes (:hover, :active, :focus), hyperlinks, and Document.elementFromPoint().</p>
</blockquote>
<p>The property was originally SVG-only, but eventually browsers and the W3C adopted a more limited version for HTML elements too.</p>
<p>It can be used in many use cases that weren’t possible before (or the solution was overly complicated), one of them being to create custom-looking <code><select></code> drop downs, by overlaying an element over the native drop down arrow (to create the custom one) and disallowing pointer events on it. Here’s a quick example:</p>
<iframe style="width: 100%; height: 200px" src="http://jsfiddle.net/leaverou/XxkSC/embedded/result,css"></iframe>
<p><code>-webkit-appearance: none</code> was needed in Webkit to turn off the native OSX appearance (in OSX and maybe Safari on Windows, I didn’t test that). However, since that also removes the native drop down arrow, our custom arrow now obscures part of the text, so we had to add a 30px padding-right to the select element, only in Webkit. You can easily detect if <code>pointer-events</code> is supported via JS and only apply this it if it is (eg by adding or removing a class from the body element):</p>
<p>if(!(‘pointerEvents’ in document.body.style)) {
…
}</p>
<p>However, there is one caveat in this: Opera does include pointerEvents in HTML elements as well, but it does not actually support the property on HTML. There’s a more elaborate feature detection script <a href="https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js">here</a> as a Modernizr plugin (but the code is quite short, so you can adapt it to your needs).</p>
<p>Also, don’t try to replicate the behavior in JavaScript for browsers that don’t support this: it’s impossible to open a <code><select></code> drop down with JavaScript. Or, to put it differently, if you manage to do it, you’ll probably be the first to. Everything I could think of failed and I spent hours yesterday searching for a way, but no avail.</p>
<h3 id="references" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/03/custom-select-drop-downs-with-css3/#references">References</a></h3>
<ul>
<li><a href="http://dev.w3.org/csswg/css3-ui/#pointer-events">W3C specification</a></li>
<li><a href="https://developer.mozilla.org/en/CSS/pointer-events">MDN article</a></li>
</ul>
WD @media talk subject change2011-03-08T00:00:00Zhttps://lea.verou.me/?p=869<p>I recently changed my <a href="http://lea.verou.me/2011/01/im-speaking-at-atmedia-web-directions-11/">Web Directions @media talk</a> title & abstract to something more specialized. Instead of discussing under-hyped CSS3 features in general I will only focus on one CSS3 feature (more hyped than the ones I was planning to show, but all the hype is only about very basic use cases): CSS3 Gradients:</p>
<blockquote>
<h3 id="mastering-css3-gradients" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/03/wd-media-talk-subject-change/#mastering-css3-gradients">Mastering CSS3 Gradients</a></h3>
<p>With most browsers adding increasing support, and the simplicity of providing fallbacks for those that don’t, CSS3 gradients are something we can start to use right now. They benefit our users with faster websites and ourselves with more time in our hands to spend in other things, since they are easy to create, edit and update. A very powerful feature that can also be utilized for a surprising number of design effects, even ones that don’t resemble gradients at all. In this talk, Lea will explore CSS3 gradients in great depth and it’s almost guaranteed that no matter your expertise level, you will walk out having learned new things.</p>
</blockquote>
<p>I tested a draft of this talk with a meetup group in Oslo (<a href="http://www.meetup.com/framsia/events/16517556/">Framsia</a>) and it went very well. I got reviews like “I was amazed that you managed to speak almost an hour of CSS3 gradients and still keep the crowd interested” (thanks Legendre!). Even <a href="http://twitter.com/brucel">Bruce Lawson</a>, who happened to be there, told me he didn’t know like 70% of the material presented! :)</p>
<p>I’m looking forward to it since it’s a topic I’m passionate about, and I hope to see you there! Don’t forget that you can use the coupon code <strong>WDVEROU</strong> when <a href="http://atmedia11.webdirections.org/">registering</a> to take £50 off the current price.</p>
<p>PS: I don’t like the title very much, so if you have anything more witty to suggest, feel free. ;)</p>
On CSS preprocessors2011-03-09T00:00:00Zhttps://lea.verou.me/?p=879<p>Lately there has been a rise in the usage of CSS preprocessors such as <a href="http://lesscss.org/">LESS</a> and <a href="http://sass-lang.com/">SASS</a>, which makes sense given the simultaneous increase of CSS3 usage. I’ve frequently argued with fellow front-end web developers about whether they should be used or not and I decided to finally put my thoughts in writing.</p>
<p>To start, I can fully understand the advantage of using such preprocessors over vanilla CSS3. I hate listing all the vendor prefixes, and not being able to use variables, mixins or nesting just like the next web developer. All this syntactic sugar can simplify your workflow by a great deal and make writing CSS3 incredibly fun. However, I still refrain from using them, and I’ll explain why below.</p>
<h3 id="losing-track-of-css-filesize" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/03/on-css-preprocessors/#losing-track-of-css-filesize">Losing track of CSS filesize</a></h3>
<p>When I’m writing CSS, I try to keep the filesize as small as possible. I’m not a filesize hypochondriac, I try to balance filesize and readability and I prefer to err on the side of the latter. I’m not one of those people that will use <code>#000</code> instead of <code>black</code> just to save a byte and I use lots of indents and newlines (later minification takes care of that). However, in cases when the readability impact is small and the filesize impact is large (and minification won’t help), I will do the optimization.</p>
<p>For example, consider the following case: Let’s suppose you have 3 rules (<code>#foo</code>, <code>#bar</code> and <code>#baz</code>) that will both use the same CSS rotate transformation, among other CSS declarations. Using a mixin is simple (using the LESS syntax in this example):</p>
<p>.rotate (@degrees: 10deg) {
-moz-transform: rotate(@degrees);
-ms-transform: rotate(@degrees);
-o-transform: rotate(@degrees);
-webkit-transform: rotate(@degrees);
transform: rotate(@degrees);
}</p>
<p>#foo {
font-size: 150%;
.rotate(40deg);
}</p>
<p>#bar {
background: silver;
.rotate(40deg);
}</p>
<p>#baz {
background: white;
.rotate(40deg);
}</p>
<p>Sweet, huh? And only 370 bytes. However, what the end user downloads is this beast:</p>
<p>#foo {
font-size: 150%;
-moz-transform: rotate(40deg);
-ms-transform: rotate(40deg);
-o-transform: rotate(40deg);
-webkit-transform: rotate(40deg);
transform: rotate(40deg);
}</p>
<p>#bar {
background: silver;
-moz-transform: rotate(40deg);
-ms-transform: rotate(40deg);
-o-transform: rotate(40deg);
-webkit-transform: rotate(40deg);
transform: rotate(40deg);
}</p>
<p>#baz {
background: white;
-moz-transform: rotate(40deg);
-ms-transform: rotate(40deg);
-o-transform: rotate(40deg);
-webkit-transform: rotate(40deg);
transform: rotate(40deg);
}</p>
<p>which is almost double the filesize (600 bytes). It could have easily been this:</p>
<p>#foo, #bar, #baz {
-moz-transform: rotate(40deg);
-ms-transform: rotate(40deg);
-o-transform: rotate(40deg);
-webkit-transform: rotate(40deg);
transform: rotate(40deg);
}</p>
<p>#foo {
font-size: 150%;
}</p>
<p>#bar {
background: silver;
}</p>
<p>#baz {
background: white;
}</p>
<p>which at 290 bytes, is even smaller than the first one. The differences would be even bigger if you had to specify a different transform-origin.</p>
<p>Of course you can still do such optimizations when using CSS preprocessors, but since you don’t have the ugliness in front of you and the file you’re working with remains small, it’s easy to forget and just do what’s easy. You lose sight of the big picture. But it’s the big picture (or big file, in this case ;)) that your users eventually download.</p>
<p>Same goes for nesting: Instead of actually putting some thought into the selectors you choose, you can just nest and let the preprocessor sort it out, usually in the straightforward but unavoidably verbose way.</p>
<p>LESS is better in this aspect, since it also offers a client-side version, so the user downloads the small file you wrote, and all the expansion is done in their machine. However, this has the (big, IMO) disadvantage that all your CSS becomes dependent on JavaScript to work and that your users have to download the LESS code, which isn’t that small: 33KB minified which is way larger than most stylesheets (granted, if you gzip, it will be smaller, but this is true for stylesheets as well).</p>
<h3 id="maintenance-woes" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/03/on-css-preprocessors/#maintenance-woes">Maintenance woes</a></h3>
<p>Eventually, CSS will start supporting all this sweetness. <a href="http://www.xanthir.com/blog/">Tab Atkins</a> has already drafted <a href="http://www.xanthir.com/blog/b49w0">a proposal</a> and soon Webkit nightlies will implement the functionality. After that, I think it’s safe to assume that within 2 years Firefox and Opera will also implement the (by then) standard and within 1-2 more even IE. Then we’ll need another 2-3 years to be able to start using it (adoption rates of new browser versions will have increased too). This means that in as little as 6 years, we might be able to use CSS variables, mixins and nesting in vanilla CSS. All the code written for today’s preprocessors will eventually have to be rewritten. Maybe even sooner, since when a standard is published, I think it’s safe to assume (or hope) that the new versions of CSS preprocessors will deprecate their old syntax and start supporting and recommending the standard way, effectively becoming polyfills (which I definitely support). So, coding for a CSS preprocessor today feels a bit like building castles on sand.</p>
<h3 id="debugging-woes-(thanks-to-jesper-ek)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/03/on-css-preprocessors/#debugging-woes-(thanks-to-jesper-ek)">Debugging woes (thanks to Jesper Ek)</a></h3>
<p>Preprocessors make debugging CSS harder, since the CSS you see in Web Inspectors like Firebug or Dragonfly is not the CSS you wrote. The line numbers don’t match any more and the CSS itself is different. A lighter form of the same problem also occurs with minifiers, but you can delay using them until you’re done with the site. With CSS preprocessors, you have to use them from the beginning if you want to really take advantage of them.</p>
<p>Also, when I develop my CSS, I want to be able to instantly preview the changes in the file by just refreshing the browser. With preprocessors this becomes harder (although not impossible).</p>
<h3 id="generic-concerns-with-such-abstractions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/03/on-css-preprocessors/#generic-concerns-with-such-abstractions">Generic concerns with such abstractions</a></h3>
<p>With every new syntax, comes more effort required by someone to start working on our code. We either have to only collaborate with people proficient in the CSS preprocessor of our choice, or teach them its syntax. So we are either restricted in our choice of collaborators or need to spend extra time for training, both of which are nuisances.</p>
<p>Also, what happens if the preprocessor stops being updated? Granted, most (if not all) are open source, but the community’s interest might shift to something else. Many open source projects have eventually died due to lack of interest. And let’s not forget <a href="http://en.wikipedia.org/wiki/Leaky_abstraction#The_Law_of_Leaky_Abstractions">the law of leaky abstractions</a>…</p>
<p>Yes, both concerns are valid for every framework, in every language, but at least PHP frameworks or JavaScript libraries are more needed than CSS preprocessors, so it’s a tradeoff is that’s worth it. For CSS preprocessors, I’m not so sure.</p>
<h3 id="conclusion-%26-disclaimer" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/03/on-css-preprocessors/#conclusion-%26-disclaimer">Conclusion <em>&</em> disclaimer</a></h3>
<p>I have to admit that even though I’ve read quite a bit on CSS preprocessors and talked with fellow web developers about them, I don’t have hands-on experience with them. Maybe I will change my mind if I actually do so. Besides, I think that if someone uses a CSS preprocessor carefully, with knowledge of the points mentioned above, it can actually turn out to be beneficial. However personally, I prefer to wait at least until they start supporting the (future) standard syntax, whenever that happens.</p>
Beveled corners & negative border-radius with CSS3 gradients2011-03-14T00:00:00Zhttps://lea.verou.me/?p=892<p>Just found out how to do beveled corners and simulate negative border radius without images, by utilizing CSS gradients once again. It’s amazing how many CSS problems can be solved with gradients alone. Read the text in the dabblet below to find out how (or just check the code):</p>
<iframe style="width: 100%; height: 800px" src="http://dabblet.com/gist/10168919"></iframe>
<p>It also falls back to a solid color background if CSS gradients are not supported. It will work on Firefox 3.6+, Chrome, Safari, Opera 11.10+ and IE10+.</p>
<p>PS: For my twitter friends, I had already written this when the robbers came and I was about to post it. I might have been really calm, but not as much as making CSS experiments the same day I was robbed and threatened by a gun :P</p>
Convert standard gradient syntax to -webkit-gradient and others2011-03-26T00:00:00Zhttps://lea.verou.me/?p=907<p><img src="https://lea.verou.me/2011/03/convert-standard-gradient-syntax-to-webkit-gradient-and-others/images/cssgradientsplease-screenshot-300x148.png" alt="Screenshot of the demo" title="cssgradientsplease - screenshot" />I hate <code>-webkit-gradient()</code> with a passion. Its syntax is cumbersome and it’s really limited: No angle support, no <code><length></code>s in color stop positions, no implied color stop positions, no elliptical gradients… So, I was really happy, when Webkit implemented the standard syntax this January. However, we’re still stuck with the horrid <code>-webkit-gradient()</code> for quite a while, since older Webkit browsers that don’t support it are widely used at this time.</p>
<p>Today, I decided to finally spare myself the hassle of converting my standard gradient syntax to -webkit-gradient() by hand. Tasks like that shouldn’t be handled by a human. So, I coded a little script to do the chore. Hope it helps you too: <a href="https://lea.verou.me/demos/cssgradientsplease/" class="call-to-action">View demo</a></p>
<p>It currently only supports linear gradients, but I plan to add radial ones in the future. Also, when I get around to cleaning up the code a bit, I’ll add it on Github.</p>
<p>(Hope I didn’t leave in any very stupid bug, it’s really late here and I’m half asleep.)</p>
Create complex RegExps more easily2011-03-28T00:00:00Zhttps://lea.verou.me/?p=916<p>When I was writing <a href="http://lea.verou.me/2011/03/convert-standard-gradient-syntax-to-webkit-gradient-and-others/">my linear-gradient() to -webkit-gradient() converter</a>, I knew in advance that I would have to use a quite large regular expression to validate and parse the input. Such a regex would be incredibly hard to read and fix potential issues, so I tried to find a way to cut the process down in reusable parts.</p>
<p>Turns out JavaScript regular expression objects have a .source property that can be used in the RegExp constructor to create a new RegExp out of another one. So I wrote a new function that takes a string with identifiers for regexp replacements in and replaces them with the corresponding sub-regexps, taken from an object literal as a second argument:</p>
<p>/**
* Create complex regexps in an easy to read way
* @param str {String} Final regex with for replacements
* @param replacements {Object} Object with the replacements
* @param flags {String} Just like the flags argument in the RegExp constructor
*/
RegExp.create = function(str, replacements, flags) {
for(var id in replacements) {
var replacement = replacements[id],
idRegExp = RegExp(’ + id + ', ‘gi’);</p>
<p>if(replacement.source) {
replacement = replacement.source.replace(/^\^|\$$/g, ‘’);
}</p>
<p>// Don’t add extra parentheses if they already exist
str = str.replace(RegExp(‘\\(’ + idRegExp.source + ‘\\)’, ‘gi’), ‘(’ + replacement + ‘)’);</p>
<p>str = str.replace(idRegExp, ‘(?:’ + replacement + ‘)’);
}</p>
<p>return RegExp(str, flags);
};</p>
<p>If you don’t like adding a function to the RegExp object, you can name it however you want. Here’s how I used it for my linear-gradient() parser:</p>
<p>self.regex = {};</p>
<p>self.regex.number = /^-?[0-9]*\.?[0-9]+<mjx-container class="MathJax" jax="SVG" style="direction: ltr; position: relative;"><svg style="overflow: visible; min-height: 1px; min-width: 1px; vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="100.705ex" height="2.587ex" role="img" focusable="false" viewBox="0 -893.3 44511.7 1143.3" aria-hidden="true"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="TeXAtom" data-mjx-texclass="ORD"><g data-mml-node="mo"><path data-c="2F" d="M423 750Q432 750 438 744T444 730Q444 725 271 248T92 -240Q85 -250 75 -250Q68 -250 62 -245T56 -231Q56 -221 230 257T407 740Q411 750 423 750Z" style="stroke-width: 3;"></path></g></g><g data-mml-node="mo" transform="translate(500,0)"><path data-c="3B" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 85 94 103T137 121Q202 121 202 8Q202 -44 183 -94T144 -169T118 -194Q115 -194 106 -186T95 -174Q94 -171 107 -155T137 -107T160 -38Q161 -32 162 -22T165 -4T165 4Q165 5 161 4T142 0Q110 0 94 18T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(944.7,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(1413.7,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(1879.7,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(2177.7,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(2727.7,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(3172.3,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(3623.3,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(4089.3,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(4566.3,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(5032.3,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(5604.3,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(6049,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(6570,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(7036,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(7526,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(8242,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(8727,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(9178,0)"><path data-c="1D451" d="M366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(9975.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z" style="stroke-width: 3;"></path></g><g data-mml-node="msup" transform="translate(11031.6,0)"><g data-mml-node="TeXAtom" data-mjx-texclass="ORD"><g data-mml-node="mo"><path data-c="2F" d="M423 750Q432 750 438 744T444 730Q444 725 271 248T92 -240Q85 -250 75 -250Q68 -250 62 -245T56 -231Q56 -221 230 257T407 740Q411 750 423 750Z" style="stroke-width: 3;"></path></g></g><g data-mml-node="mo" transform="translate(533,363) scale(0.707)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g></g><g data-mml-node="mo" transform="translate(11889.6,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(12639.4,0)"><path data-c="3A" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(13195.2,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(13556.2,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(14041.2,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z" style="stroke-width: 3;"></path></g><g data-mml-node="mspace" transform="translate(14544.2,0)"></g><g data-mml-node="mi" transform="translate(14544.2,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(15235.4,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(16235.6,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(16513.6,0)"><path data-c="1D44F" d="M73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(16942.6,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(17427.6,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(17788.6,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(18149.6,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(18634.6,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mspace" transform="translate(19512.6,0)"></g><g data-mml-node="mi" transform="translate(19512.6,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(19981.6,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(20759.6,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(21148.6,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(21620.6,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(22009.6,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(22759.4,0)"><path data-c="3A" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(23315.2,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(23766.2,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(24111.2,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(24588.2,0)"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(25164.2,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(25525.2,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(25803.2,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(26101.2,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(26567.2,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(27117.2,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(27478.2,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(27867.2,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(28145.2,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(28534.2,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(29284,0)"><path data-c="3A" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(29839.7,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(30290.7,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(30635.7,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(31112.7,0)"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(31688.7,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mspace" transform="translate(32049.7,0)"></g><g data-mml-node="mi" transform="translate(32049.7,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(32741,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(33741.2,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(34019.2,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(34317.2,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(34783.2,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(35333.2,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mspace" transform="translate(35694.2,0)"></g><g data-mml-node="mi" transform="translate(35694.2,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(36163.2,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(36941.2,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(37330.2,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(37802.2,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(38191.2,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(38941,0)"><path data-c="3A" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(39496.7,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(39857.7,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(40342.7,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(40845.7,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(41123.7,0)"><path data-c="1D44F" d="M73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(41552.7,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(42037.7,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(42398.7,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(42759.7,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(43244.7,0)"><path data-c="1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(44122.7,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z" style="stroke-width: 3;"></path></g></g></g></svg><mjx-assistive-mml unselectable="on" display="inline" style="top: 0px; left: 0px; clip: rect(1px, 1px, 1px, 1px); -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; position: absolute; padding: 1px 0px 0px 0px; border: 0px; display: block; width: auto; overflow: hidden;"><math xmlns="http://www.w3.org/1998/Math/MathML"><mrow data-mjx-texclass="ORD"><mo>/</mo></mrow><mo>;</mo><mi>s</mi><mi>e</mi><mi>l</mi><mi>f</mi><mo>.</mo><mi>r</mi><mi>e</mi><mi>g</mi><mi>e</mi><mi>x</mi><mo>.</mo><mi>k</mi><mi>e</mi><mi>y</mi><mi>w</mi><mi>o</mi><mi>r</mi><mi>d</mi><mo>=</mo><msup><mrow data-mjx-texclass="ORD"><mo>/</mo></mrow><mo stretchy="false">(</mo></msup><mo>?</mo><mo>:</mo><mi>t</mi><mi>o</mi><mi>p</mi><mspace linebreak="newline"></mspace><mi>s</mi><mo>+</mo><mo data-mjx-texclass="ORD" stretchy="false">|</mo><mi>b</mi><mi>o</mi><mi>t</mi><mi>t</mi><mi>o</mi><mi>m</mi><mspace linebreak="newline"></mspace><mi>s</mi><mo>+</mo><mo stretchy="false">)</mo><mo>?</mo><mo stretchy="false">(</mo><mo>?</mo><mo>:</mo><mi>r</mi><mi>i</mi><mi>g</mi><mi>h</mi><mi>t</mi><mo data-mjx-texclass="ORD" stretchy="false">|</mo><mi>l</mi><mi>e</mi><mi>f</mi><mi>t</mi><mo stretchy="false">)</mo><mo data-mjx-texclass="ORD" stretchy="false">|</mo><mo stretchy="false">(</mo><mo>?</mo><mo>:</mo><mi>r</mi><mi>i</mi><mi>g</mi><mi>h</mi><mi>t</mi><mspace linebreak="newline"></mspace><mi>s</mi><mo>+</mo><mo data-mjx-texclass="ORD" stretchy="false">|</mo><mi>l</mi><mi>e</mi><mi>f</mi><mi>t</mi><mspace linebreak="newline"></mspace><mi>s</mi><mo>+</mo><mo stretchy="false">)</mo><mo>?</mo><mo stretchy="false">(</mo><mo>?</mo><mo>:</mo><mi>t</mi><mi>o</mi><mi>p</mi><mo data-mjx-texclass="ORD" stretchy="false">|</mo><mi>b</mi><mi>o</mi><mi>t</mi><mi>t</mi><mi>o</mi><mi>m</mi><mo stretchy="false">)</mo></math></mjx-assistive-mml></mjx-container>/;</p>
<p>self.regex.direction = RegExp.create(‘^(?:|deg|0)$’, {
keyword: self.regex.keyword,
number: self.regex.number
});</p>
<p>self.regex.color = RegExp.create(‘(?:||)’, {
keyword: /^(?:red|tan|grey|gray|lime|navy|blue|teal|aqua|cyan|gold|peru|pink|plum|snow|[a-z]{5,20})<mjx-container class="MathJax" jax="SVG" style="direction: ltr; position: relative;"><svg style="overflow: visible; min-height: 1px; min-width: 1px; vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="47.042ex" height="2.587ex" role="img" focusable="false" viewBox="0 -893.3 20792.5 1143.3" aria-hidden="true"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="TeXAtom" data-mjx-texclass="ORD"><g data-mml-node="mo"><path data-c="2F" d="M423 750Q432 750 438 744T444 730Q444 725 271 248T92 -240Q85 -250 75 -250Q68 -250 62 -245T56 -231Q56 -221 230 257T407 740Q411 750 423 750Z" style="stroke-width: 3;"></path></g></g><g data-mml-node="mo" transform="translate(500,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(944.7,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(1494.7,0)"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(2066.7,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(2666.7,0)"><path data-c="1D450" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(3377.4,0)"><path data-c="3A" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(3933.2,0)"><path data-c="1D445" d="M230 637Q203 637 198 638T193 649Q193 676 204 682Q206 683 378 683Q550 682 564 680Q620 672 658 652T712 606T733 563T739 529Q739 484 710 445T643 385T576 351T538 338L545 333Q612 295 612 223Q612 212 607 162T602 80V71Q602 53 603 43T614 25T640 16Q668 16 686 38T712 85Q717 99 720 102T735 105Q755 105 755 93Q755 75 731 36Q693 -21 641 -21H632Q571 -21 531 4T487 82Q487 109 502 166T517 239Q517 290 474 313Q459 320 449 321T378 323H309L277 193Q244 61 244 59Q244 55 245 54T252 50T269 48T302 46H333Q339 38 339 37T336 19Q332 6 326 0H311Q275 2 180 2Q146 2 117 2T71 2T50 1Q33 1 33 10Q33 12 36 24Q41 43 46 45Q50 46 61 46H67Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628Q287 635 230 637ZM630 554Q630 586 609 608T523 636Q521 636 500 636T462 637H440Q393 637 386 627Q385 624 352 494T319 361Q319 360 388 360Q466 361 492 367Q556 377 592 426Q608 449 619 486T630 554Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(4692.2,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(5158.2,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(5635.2,0)"><path data-c="1D438" d="M492 213Q472 213 472 226Q472 230 477 250T482 285Q482 316 461 323T364 330H312Q311 328 277 192T243 52Q243 48 254 48T334 46Q428 46 458 48T518 61Q567 77 599 117T670 248Q680 270 683 272Q690 274 698 274Q718 274 718 261Q613 7 608 2Q605 0 322 0H133Q31 0 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 659Q197 676 203 680H757Q764 676 764 669Q764 664 751 557T737 447Q735 440 717 440H705Q698 445 698 453L701 476Q704 500 704 528Q704 558 697 578T678 609T643 625T596 632T532 634H485Q397 633 392 631Q388 629 386 622Q385 619 355 499T324 377Q347 376 372 376H398Q464 376 489 391T534 472Q538 488 540 490T557 493Q562 493 565 493T570 492T572 491T574 487T577 483L544 351Q511 218 508 216Q505 213 492 213Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(6399.2,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(6971.2,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(7474.2,0)"><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(7918.9,0)"><path data-c="1D450" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(8351.9,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(8802.9,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(9268.9,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(9797.9,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(10158.9,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"></path></g><g data-mml-node="msup" transform="translate(10624.9,0)"><g data-mml-node="mo"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mrow" transform="translate(422,363) scale(0.707)"><g data-mml-node="mo"><path data-c="2032" d="M79 43Q73 43 52 49T30 61Q30 68 85 293T146 528Q161 560 198 560Q218 560 240 545T262 501Q262 496 260 486Q259 479 173 263T84 45T79 43Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(275,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g></g></g><g data-mml-node="mo" transform="translate(11566.4,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(12316.2,0)"><path data-c="3A" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(12872,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(13323,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(13800,0)"><path data-c="1D44F" d="M73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(14229,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(14507,0)"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(15083,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(15552,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(15850,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z" style="stroke-width: 3;"></path></g><g data-mml-node="mi" transform="translate(16239,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(16768,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mspace" transform="translate(17240,0)"></g><g data-mml-node="mspace" transform="translate(17240,0)"></g><g data-mml-node="mo" transform="translate(17240,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(17629,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(18018,0)"><path data-c="3F" d="M226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61Z" style="stroke-width: 3;"></path></g><g data-mml-node="mo" transform="translate(18767.7,0)"><path data-c="3A" d="M78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" style="stroke-width: 3;"></path></g><g data-mml-node="mspace" transform="translate(19045.7,0)"></g><g data-mml-node="mspace" transform="translate(19045.7,0)"></g><g data-mml-node="mi" transform="translate(19323.5,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"></path></g><g data-mml-node="mtext" fill="red" stroke="red" transform="translate(19792.5,0)"><path data-c="5C" d="M56 731Q56 740 62 745T75 750Q85 750 92 740Q96 733 270 255T444 -231Q444 -239 438 -244T424 -250Q414 -250 407 -240Q404 -236 230 242T56 731Z" style="stroke-width: 3;"></path><path data-c="2A" d="M215 721Q216 732 225 741T248 750Q263 750 273 742T284 721L270 571L327 613Q383 654 388 657T399 660Q412 660 423 650T435 624T424 600T376 575Q363 569 355 566L289 534L355 504L424 470Q435 462 435 447Q435 431 424 420T399 409Q393 409 388 412T327 456L270 498L277 423L284 348Q280 320 250 320T215 348L229 498L172 456Q116 415 111 412T100 409Q87 409 76 420T64 447Q64 461 75 470L144 504L210 534L144 566Q136 570 122 576Q83 593 74 600T64 624Q64 639 75 649T100 660Q106 660 111 657T172 613L229 571Q229 578 222 643T215 721Z" transform="translate(500,0)" style="stroke-width: 3;"></path></g></g></g></svg><mjx-assistive-mml unselectable="on" display="inline" style="top: 0px; left: 0px; clip: rect(1px, 1px, 1px, 1px); -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; position: absolute; padding: 1px 0px 0px 0px; border: 0px; display: block; width: auto; overflow: hidden;"><math xmlns="http://www.w3.org/1998/Math/MathML"><mrow data-mjx-texclass="ORD"><mo>/</mo></mrow><mo>,</mo><mi>f</mi><mi>u</mi><mi>n</mi><mi>c</mi><mo>:</mo><mi>R</mi><mi>e</mi><mi>g</mi><mi>E</mi><mi>x</mi><mi>p</mi><mo>.</mo><mi>c</mi><mi>r</mi><mi>e</mi><mi>a</mi><mi>t</mi><mi>e</mi><msup><mo stretchy="false">(</mo><mrow><mo data-mjx-alternate="1" data-mjx-pseudoscript="true">′</mo><mo stretchy="false">(</mo></mrow></msup><mo>?</mo><mo>:</mo><mi>r</mi><mi>g</mi><mi>b</mi><mo data-mjx-texclass="ORD" stretchy="false">|</mo><mi>h</mi><mi>s</mi><mi>l</mi><mo stretchy="false">)</mo><mi>a</mi><mo>?</mo><mspace linebreak="newline"></mspace><mspace linebreak="newline"></mspace><mo stretchy="false">(</mo><mo stretchy="false">(</mo><mo>?</mo><mo>:</mo><mspace linebreak="newline"></mspace><mspace linebreak="newline"></mspace><mi>s</mi><mtext mathcolor="red">\*</mtext></math></mjx-assistive-mml></mjx-container>', {
number: self.regex.number
}),
hex: /^#(?:[0-9a-f]{1,2}){3}$/
});</p>
<p>self.regex.percentage = RegExp.create(‘^(?:%|0)$’, {
number: self.regex.number
});</p>
<p>self.regex.length = RegExp.create(‘|0’, {
number: self.regex.number,
unit: /%|px|mm|cm|in|em|rem|en|ex|ch|vm|vw|vh/
});</p>
<p>self.regex.colorStop = RegExp.create(‘\\s*?’, {
color: self.regex.color,
length: self.regex.length
}, ‘g’);</p>
<p>self.regex.linearGradient = RegExp.create(‘^linear-gradient\\(\\s*(?:()\\s*,)?\\s*(\\s*(?:,\\s*\\s*)+)\\)$’, {
direction: self.regex.direction,
colorStop: self.regex.colorStop
}, ‘i’);</p>
<p>(self in this case was a local variable, not the window object)</p>
Invert a whole webpage with CSS only2011-04-14T00:00:00Zhttps://lea.verou.me/?p=920<p><a href="https://lea.verou.me/2011/04/invert-a-whole-webpage-with-css-only/images/Screen-shot-2011-04-14-at-22.24.18-.png"><img src="https://lea.verou.me/2011/04/invert-a-whole-webpage-with-css-only/images/Screen-shot-2011-04-14-at-22.24.18--300x199.png" alt="" title="Screenshot from the effect applied on my blog" /></a>I recently saw <a href="https://gist.github.com/373253">Paul Irish’s jQuery invert page plugin</a>. It inverts every color on a webpage including images or CSS. This reminded me of the invert color keyword that’s allowed on outlines (and sadly only supported by Opera and IE9+). So I wondered how it could be exploited to achieve the same effect through CSS alone. Turned out to be quite simple actually:</p>
<p>body:before {
content:“”;
position:fixed;
top:50%; left: 50%;
z-index:9999;
width:1px; height: 1px;
outline:2999px solid invert;
}</p>
<p>Not even <code>pointer-events:none;</code> is needed, since outlines don’t receive pointer events anyway, and there’s no issue with scrollbars since they don’t contribute to scrolling. So this is not even CSS3, it’s just plain ol’ CSS 2.1.</p>
<p>And here’s a bookmarklet to inject it into any given page: [Invert page](javascript:(function(){var%20style=document.createElement(‘style’);style.innerHTML=‘body:before%20{%20content:%22%22;%20position:fixed;%20top:50%25;%20left:50%25;%20z-index:9999;%20width:1px;%20height:%201px;%20outline:2999px%20solid%20invert;%20}’;document.body.appendChild(style)})();)</p>
<p>**Note:**This will only work on Opera and IE9+ since they’re currently the only ones supporting the color keyword ‘invert’ on outlines. However, it’s probably possible to add Firefox support too with SVG filters, since they support them on HTML elements as well.</p>
<p>As for why would someone want to invert a page… I guess it could be useful for people that can read white text on dark backgrounds more easily, April fools jokes, konami code fun and stuff like that.</p>
<p><strong>Update:</strong> Mozilla is planning to <strong>never</strong> support <code>invert</code> because there’s a <a href="http://www.w3.org/TR/CSS21/ui.html#propdef-outline-color">loophole in the CSS 2.1 spec</a> that allows them to do that. However, you can push them to support it by <a href="https://bugzilla.mozilla.org/votes.cgi?action=show_user&bug_id=359497#vote_359497">voting</a> on the <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=359497">relevant issue</a>.</p>
CSS3 patterns gallery and a new pattern2011-04-15T00:00:00Zhttps://lea.verou.me/?p=932<p><a href="https://lea.verou.me/2011/04/css3-patterns-gallery-and-a-new-pattern/images/csspatterns.png"><img src="https://lea.verou.me/2011/04/css3-patterns-gallery-and-a-new-pattern/images/csspatterns-300x177.png" alt="" title="Screenshot from the gallery" /></a>I finally got around to doing what I wanted to do for quite a few months: Create a gallery with all the basic patterns I was able to create with CSS3 gradients. Here it is: <a href="http://lea.verou.me/css3patterns/">CSS3 Pattern Gallery</a></p>
<p>Also, it includes a brand new pattern, which is the hardest one I have ever made so far: Japanese cubes. Thanks to <a href="http://twitter.com/dstorey">David Storey</a> for challenging me about it.</p>
<p>Supported browsers:</p>
<ul>
<li>Firefox 4 (the patterns themselves work on 3.6 too but the gallery doesn’t due to a JS limitation)</li>
<li>Opera 11.10</li>
<li>IE10</li>
<li>Google Chrome</li>
<li>Webkit nightlies</li>
</ul>
<p>However bear in mind that every implementation has its limitations so a few of them won’t work in all the aforementioned browsers (for example Opera doesn’t support radial gradients and Firefox doesn’t support explicitly sized ones).</p>
Rule filtering based on specific selector(s) support2011-05-02T00:00:00Zhttps://lea.verou.me/?p=966<p>I’ve been using this trick for quite a while, but I never thought to blog about it. However, I recently realized that it might not be as common as I thought, so it might be a good idea to document it in a blog post.</p>
<p>If you follow the discussions on www-style, you might have noticed the <a href="http://lists.w3.org/Archives/Public/www-style/2011Apr/0428.html">proposal for a @supports rule</a> to query property and value support. Some people suggested that it should also test for selectors, for example whether a certain pseudo-class is supported. However, you can do that today, albeit in a limited manner (no OR and NOT support).</p>
<p>The main principle that you need to keep in mind is that <strong>browsers are expected to drop rules with selectors they don’t understand, even partially</strong>. So, if only one selector in a group cannot be parsed, the whole rule will be dropped. This means we can construct selector “tests”, which are use cases of the selector whose support we want to test, that will not match anything, even if the selector is supported. Then, we include that selector in the beginning of our selector group. If all this is unclear, don’t worry, as there’s an example coming next :)</p>
<h2 id="example" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/rule-filtering-based-on-specific-selectors-support/#example">Example</a></h2>
<p>Suppose you want to apply the following CSS (for rudimentary custom checkboxes):</p>
<p>input[type=“checkbox”] {
position:absolute;
clip: rect(0,0,0,0);
clip: rect(0 0 0 0);
}</p>
<p>input[type=“checkbox”] + label::before {
content: url(‘checkbox.png’);
}</p>
<p>input[type=“checkbox”]:checked + label::before {
content: url(‘checkbox-checked.png’);
}</p>
<p>only in browsers that support the attribute equality selector, the <code>:checked</code> pseudo-class and the <code>::before</code> pseudo-element. We need to try to think of a selector that includes all of them but matches nothing. One such selector would be <code>#foo[type="checkbox"]:checked::before</code>. Even in supporting browsers, this matches nothing as there’s no element with id=“foo”. We can reduce the test for every rule to conserve bandwidth: For example, we don’t need to include tests for the attribute selector in any of them, since they are present anyway in all three rules. Also, we may eliminate <code>::before</code> from the second test and we don’t need any test for the 3rd one, since it includes all features we want to test for. To sum up:</p>
<p><strong>#foo:checked::before,</strong>
input[type=“checkbox”] {
position:absolute;
clip: rect(0,0,0,0);
clip: rect(0 0 0 0);
}</p>
<p><strong>#foo:checked,</strong>
input[type=“checkbox”] + label::before {
content: url(‘checkbox.png’);
}</p>
<p>input[type=“checkbox”]:checked + label::before {
content: url(‘checkbox-checked.png’);
}</p>
<p>An important caveat of this technique is that <strong>Internet Explorer up to version 7</strong> will split selectors before parsing them, so it will completely ignore our filters :( (Thanks to <a href="http://www.thecssninja.com/">Ryan Seddon</a> for finding that out).</p>
<p>Disclaimer: The original idea about custom checkboxes belongs to <a href="http://www.thecssninja.com/css/custom-inputs-using-css">Ryan Seddon</a>, although his code was quite different.</p>
StronglyTyped: A library for strongly typed properties & constants in JavaScript2011-05-05T00:00:00Zhttps://lea.verou.me/?p=983<p><a href="http://www.flickr.com/photos/leaverou/5691500699/in/photostream"><img src="https://lea.verou.me/2011/05/strongly-typed-javascript/images/strongly-typed-300x210.png" alt="StronglyTyped" title="StronglyTyped logo" /></a>I’ll start by saying I <strong>love</strong> the loosely typed nature of JavaScript. When I had to work with strongly typed languages like Java, it always seemed like an unnecessary hassle. On the contrary, my boyfriend even though very proficient with HTML, CSS and SVG, comes from a strong Java background and hates loosely typed scripting languages. So, to tempt him into JS and keep him away from heavy abstractions like Objective-J, I wrote a little library that allows you to specify strongly typed properties (and since global variables are also properties of the window object, those as well) of various types (real JS types like <code>Boolean</code>, <code>Number</code>, <code>String</code> etc or even made up ones like <code>Integer</code>) and constants (final properties in Java). It uses ES5 getters and setters to do that and falls back to regular, loosely typed properties in non-supporting browsers.</p>
<p>Also, as a bonus, you get cross-browser <code>Function.prototype.bind</code> and <code>Array.prototype.forEach</code> and a robust type checking function: <code>StronglyTyped.is(type, value)</code>.</p>
<h2 id="example%3A-strongly-typed-properties" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/strongly-typed-javascript/#example%3A-strongly-typed-properties">Example: Strongly typed properties</a></h2>
<p>You define strongly typed properties by using the corresponding methods of the <code>StronglyTyped</code> object. For example, the following snippet defines a boolean property called “foo” on an object literal:</p>
<p>var o = {};</p>
<p>StronglyTyped.boolean(o, ‘foo’, true);</p>
<p>console.log(o.foo); // prints true</p>
<p>o.foo = false;
console.log(o.foo); // prints false</p>
<p>o.foo = ‘bar’; // <strong>TypeError: foo must be of type Boolean. bar is not.</strong></p>
<h2 id="example%3A-constants" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/strongly-typed-javascript/#example%3A-constants">Example: Constants</a></h2>
<p>You define constants by using the <code>constant</code> method of the <code>StronglyTyped</code> object. For example, the following snippet defines a global MAGIC_NUMBER constant:</p>
<p>var o = {};</p>
<p>StronglyTyped.constant(window, ‘MAGIC_NUMBER’, 3.1415926535);</p>
<p>console.log(MAGIC_NUMBER); // prints 3.1415926535</p>
<p>MAGIC_NUMBER = 4;
console.log(MAGIC_NUMBER); // prints 3.1415926535</p>
<p>Please note that constants only become read-only after they first get a non-undefined value. For example:</p>
<p>StronglyTyped.constant(window, ‘MAGIC_NUMBER’);</p>
<p>console.log(MAGIC_NUMBER); // prints undefined</p>
<p>MAGIC_NUMBER = undefined;</p>
<p>console.log(MAGIC_NUMBER); // prints undefined</p>
<p>MAGIC_NUMBER = 3.1415926535;
console.log(MAGIC_NUMBER); // prints 3.1415926535</p>
<p>MAGIC_NUMBER = 4;
console.log(MAGIC_NUMBER); // prints 3.1415926535</p>
<h2 id="supported-types" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/strongly-typed-javascript/#supported-types">Supported types</a></h2>
<p>The property types currently supported by StronglyTyped are:</p>
<ul>
<li>Array</li>
<li>Boolean</li>
<li>Date</li>
<li>Function</li>
<li>Integer</li>
<li>Number</li>
<li>RegExp</li>
<li>String</li>
</ul>
<p><code>null</code> and <code>undefined</code> are valid in every type. <code>NaN</code> and <code>Infinity</code> values are accepted in both the Number and the Integer types.</p>
<p>If you want to use a type that’s not among the above but either is native to the browser (for example <code>Element</code>) or a global object, you can use the generic method <code>StronglyTyped.property(type, object, property [, initialValue])</code>:</p>
<p>var o = {};</p>
<p>StronglyTyped.property(‘Element’, o, ‘foo’, document.body);</p>
<p>console.log(o.foo); // prints a representation of the <code><body></code> element</p>
<p>o.foo = document.head;
console.log(o.foo); // prints a representation of the <code><head></code> element</p>
<p>o.foo = 5; // <strong>TypeError: foo must be of type Element. 5 is not.</strong></p>
<h2 id="browser-support" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/strongly-typed-javascript/#browser-support">Browser support</a></h2>
<p>It should work on every browser that supports <code>Object.defineProperty</code> or <code>__defineGetter__</code> and <code>__defineSetter__</code>. As you can see from kangax’s awesome compatibility tables for <a href="http://kangax.github.com/es5-compat-table/">Object.defineProperty</a> and <a href="http://kangax.github.com/es5-compat-table/non-standard/">__define(G|S)etter__</a>, those are:</p>
<ul>
<li>Firefox 3.5+</li>
<li>IE8 (only on DOM elements)</li>
<li>IE9+</li>
<li>Opera 10.5+</li>
<li>Chrome 5+</li>
<li>Safari 4+</li>
<li>Konqueror 4.4+</li>
</ul>
<p>However, it’s only verified to work in:</p>
<ul>
<li>Firefox 4 (Win and OSX)</li>
<li>IE9+</li>
<li>Opera 11.10 for OSX, Opera 11 for Windows</li>
<li>Chrome (Win and OSX)</li>
<li>Safari 5 (Win and OSX)</li>
</ul>
<p><strong>This doesn’t mean it won’t work in the rest</strong>, just that it hasn’t been tested there (yet). You can load <a href="http://leaverou.github.com/StronglyTyped/">the unit tests (sort of…)</a> in a browser you want to test and let me know about the results. :)</p>
<h2 id="naice!-can-i-haz%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/strongly-typed-javascript/#naice!-can-i-haz%3F">Naice! Can I haz?</a></h2>
<p>As usual, you can get it from Github: <a href="https://github.com/LeaVerou/StronglyTyped">Github repo</a></p>
<h2 id="credits" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/strongly-typed-javascript/#credits">Credits</a></h2>
<p>Thanks a lot to Max (<a href="http://twitter.com/suprMax">@suprMax</a>) for Windows testing!</p>
My experience from Geek Meet2011-05-13T00:00:00Zhttps://lea.verou.me/?p=1030<p>I decided to start writing a blog post after every talk I give, to be able to go back and remember what I thought about each event, what feedback my talk got etc. And I’m starting with <a href="http://robertnyman.com/2011/04/06/geek-meet-may-2011-with-lea-verou/">Geek Meet May 2011</a>.</p>
<h2 id="the-event" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/my-experience-from-geek-meet/#the-event">The event</a></h2>
<p>Geek Meet is a meetup organized in Stockholm by <a href="http://robertnyman.com/">Robert Nyman</a>. It has hosted talks by many industry leaders like <a href="http://robertnyman.com/2011/01/12/geek-meet-february-2011-with-jake-archibald/">Jake Archibald</a>, <a href="http://robertnyman.com/2010/09/06/geek-meet-september-2010-with-bruce-lawson/">Bruce Lawson</a>, <a href="http://robertnyman.com/2009/09/21/geek-meet-october-2009-molly-holzschlag-will-present-about-html-5-and-other-goodies/">Molly Holzschlag</a>, <a href="http://robertnyman.com/2009/05/04/geek-meet-charity-june-4th-2009-chris-mills-and-remy-sharp-speaking/">Chris Mills, Remy Sharp</a>, <a href="http://robertnyman.com/2008/10/16/geek-meet-december-2008-chris-heilmann-presentations/">Christian Heilmann</a> and more. It’s free to attend and has sponsors so it can afford to offer free food, drinks and speaker accommodation.</p>
<h2 id="my-experience" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/my-experience-from-geek-meet/#my-experience">My experience</a></h2>
<p>I was very surprised to hear that the event was sold out just 18 minutes after Robert’s announcement! According to him, that set a new record for it!</p>
<p>This event was kinda challenging in many ways. I was the only speaker, so if I failed, everyone would notice. Also, I had to give 2 talks and one of them was brand new, which is always stressful.</p>
<p>However, the crowd there was awesome! Not only they were very relaxed, but they had a great sense of humor too. I don’t think I had ever been in an event that was so relaxed. And their reaction to my talks was so encouraging, I don’t think I have ever heard such loud clapping in my life!</p>
<p>I’m saving the feedback I got here, to bottle the feeling:</p>
Change URL hash without page jump2011-05-13T00:00:00Zhttps://lea.verou.me/?p=1053<p>In modern complex layouts, sometimes the point where a hash will transport you to will be entirely different than the one you actually wanted. If you prevent the default event, you will save yourself from the page jump, but the hash won’t change either. You can accept the regular behavior and change scrollTop after the jump, but the user will still see a distracting flicker. <a href="http://css-tricks.com/">Chris Coyier</a> found <a href="http://css-tricks.com/hash-tag-links-padding/">a great workaround</a> last year but it’s not meant for every case.</p>
<h2 id="a-different-solution" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/change-url-hash-without-page-jump/#a-different-solution">A different solution</a></h2>
<p>Turns out we can take advantage of the <a href="http://www.w3.org/TR/html5/history.html#the-history-interface">History API</a> to do that quite easily. It’s just one line of code:</p>
<p>history.pushState(null, null, ‘#myhash’);</p>
<p>and we can combine it with the old method of setting location.hash to cater for older browsers as well:</p>
<p>if(history.pushState) {
history.pushState(null, null, ‘#myhash’);
}
else {
location.hash = ‘#myhash’;
}</p>
<h2 id="browser-support%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/change-url-hash-without-page-jump/#browser-support%3F">Browser support?</a></h2>
<p>The History API is supported by:</p>
<ul>
<li>Firefox 4+</li>
<li>Safari 5+</li>
<li>Chrome 8+</li>
<li>Coming soon in Opera</li>
</ul>
<p>Enjoy :)</p>
Get your hash — the bulletproof way2011-05-23T00:00:00Zhttps://lea.verou.me/?p=1065<p>This is probably one of the things that everyone thinks they know how to do but many end up doing it wrong. After coming accross yet one more super fragile snippet of code for this, I decided a blog post was in order.</p>
<h2 id="the-problem" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/#the-problem">The problem</a></h2>
<p>You want to remove the pound sign (#) from <code>location.hash</code>. For example, when the hash is <code>"#foo"</code>, you want to get a string containing <code>"foo"</code>. That’s really simple, right?</p>
<h2 id="tricky-cases" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/#tricky-cases">Tricky cases</a></h2>
<p>What most developers seem to miss is that in modern, JavaScript-heavy applications, a hash can contain any unicode character. It doesn’t necessarily have to correspond to the value of an actual id attribute in the page. And even when it does, <a href="http://mathiasbynens.be/notes/html5-id-class">ID attributes can now contain almost any unicode character</a>. Another thing sometimes forgotten is that there might be no hash in the page. Even in a URL that ends in #, <code>location.hash</code> is actually equal to <code>""</code> (the empty string) and not <code>"#"</code>.</p>
<h2 id="naive-approaches" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/#naive-approaches">Naive approaches</a></h2>
<p>This one is the most recent, found in a book I was tech reviewing:</p>
<p>var hash = location.hash.match(/#(\w+)/)[1];</p>
<p>which has quite a few issues:</p>
<ul>
<li>Returns wrong results when there is any non-latin or non-alphanumeric character in the hash. For example, for the hash <code>#foo@o#bar$%huh hello</code>, just <code>"foo"</code> would be returned.</li>
<li>Throws a <code>TypeError</code> when <code>location.hash</code> is empty, since <code>.match()</code> will return <code>null</code>.</li>
</ul>
<p>Other variations of this pattern I’ve seen include using explicitly defined character classes instead of <code>\w</code>, adding an anchor (<code>^</code>) before the pound sign (which is an excellent idea for performance) and checking if <code>.match()</code> actually returned something before using its result. However, they usually also fall into at least one of the 2 aforementioned issues.</p>
<p>Another approach a friend of mine once used was this:</p>
<p>var hash = location.hash.split(‘#’)[1];</p>
<p>This also has its issues, which are ironically less than the first one, even though it seems a far more naive approach.</p>
<ul>
<li>With the same test hash, it would at least get the <code>"foo@o"</code> part, which means it only fails when the hash contains a pound sign</li>
<li>When there’s no hash, it doesn’t throw an error, although it returns <code>undefined</code> instead of the empty string.</li>
</ul>
<h2 id="getting-it-right" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/#getting-it-right">Getting it right</a></h2>
<p>The approach I usually use is far simpler than both of the above and probably looks too loose:</p>
<p>var hash = location.hash.substring(1);</p>
<p>However, let’s examine it a bit:</p>
<ul>
<li>With our weird test hash, it actually returns the correct result: “foo@o#bar$%huh hello”</li>
<li>When no hash exists, it correctly returns the empty string</li>
</ul>
<p>“But it assumes there’s a pound sign at the start of the string!” I almost hear some of you cry. Well, that could be a real concern, if we were dealing with an arbitrary string. In that case, we would have to check if there’s actually a pound sign first or if the string even exists. However, with <code>location.hash</code> the only case when that is not true, is when there is no hash. And we got that case covered. ;)</p>
<p><strong>Edit:</strong> As <a href="http://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/#comment-209660879">pointed out</a> in the comments, you may also use <code>location.hash.slice(1)</code> instead of <code>substring</code>. I kinda prefer it, since it’s 4 bytes shorter.</p>
<p>If however you’re obsessed with RegExps and want to do it with them no matter what, this is just as bulletproof and almost as short:</p>
<p>var hash = location.hash.replace(/^#/, ‘’);</p>
<p>If for some reason (OCD?) you want to do it with <code>.match()</code> no matter what, you could do this:</p>
<p>var match = location.hash.match(/^#?(.*)$/)[1];</p>
<p>In that case, since the pound sign is optional, since <code>.match()</code> never returns <code>null</code>. And no, the pound sign never erroneously becomes part of the returned hash, because of the way regex engines work.</p>
<h2 id="%E2%80%9Cthis-is-too-basic%2C-what-a-waste-of-my-time!%E2%80%9D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/#%E2%80%9Cthis-is-too-basic%2C-what-a-waste-of-my-time!%E2%80%9D">“This is too basic, what a waste of my time!”</a></h2>
<p>Sorry for that. I know that for some of you, this is elementary. But the guy who wrote that book is very knowledgable (the book is really good, apart from that code snippet) so I thought this means there are many good developers out there who get this wrong, so this post was needed to be written. If you’re not one of them, you can take it as a compliment.</p>
<h2 id="%E2%80%9Chey%2C-you-missed-something-too!%E2%80%9D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/#%E2%80%9Chey%2C-you-missed-something-too!%E2%80%9D">“Hey, you missed something too!”</a></h2>
<p>In that case, I’d love to find out what it is, so please leave a comment! :)</p>
My experience from Web Directions @media & Standards.next2011-06-01T00:00:00Zhttps://lea.verou.me/?p=1087<p>Last week, I was in London to give 2 talks. The first one was last Thursday, in one of the conferences I wanted to go ever since I learned my first CSS properties: <a href="http://atmedia11.webdirections.org/">Web directions @media</a>. The second one was 2 days later in a smaller event called <a href="http://standards-next.org/">Standards.next</a>.</p>
<h2 id="web-directions-%40media" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/06/my-experience-from-web-directions-media-standards-next/#web-directions-%40media">Web Directions @media</a></h2>
<p>I managed to get my @media talk early on schedule, so I could relax afterwards and enjoy the rest of the conference. Before I saw the feedback on twitter I thought they hated it, since the audience was silent and didn’t laugh at any of my jokes and asked no questions afterwards. However, I was wrong: The tweets about it were enthusiastic! Here’s a small sample:</p>
<p><a href="https://twitter.com/KrokoHunter/status/73686696381779968">https://twitter.com/KrokoHunter/status/73686696381779968</a></p>
<p><a href="https://twitter.com/adrmakow/status/73688461537849344">https://twitter.com/adrmakow/status/73688461537849344</a></p>
<p><a href="https://twitter.com/KrokoHunter/status/73691398058147840">https://twitter.com/KrokoHunter/status/73691398058147840</a></p>
<p><a href="https://twitter.com/xtinafowler/status/73691708138848256">https://twitter.com/xtinafowler/status/73691708138848256</a></p>
<p><a href="https://twitter.com/stefsull/status/73692171684941825">https://twitter.com/stefsull/status/73692171684941825</a></p>
<p><a href="https://twitter.com/ubelly/status/73692212772343808">https://twitter.com/ubelly/status/73692212772343808</a></p>
<p><a href="https://twitter.com/pauliom/status/73694312315092992">https://twitter.com/pauliom/status/73694312315092992</a></p>
<p><a href="https://twitter.com/mfujica/status/73694696517545984">https://twitter.com/mfujica/status/73694696517545984</a></p>
<p><a href="https://twitter.com/johnallsopp/status/73694876524490752">https://twitter.com/johnallsopp/status/73694876524490752</a></p>
<p><a href="https://twitter.com/johnallsopp/status/73695196986085376">https://twitter.com/johnallsopp/status/73695196986085376</a></p>
<p><a href="https://twitter.com/prawnstar/status/73697565970935808">https://twitter.com/prawnstar/status/73697565970935808</a></p>
<p><a href="https://twitter.com/KrokoHunter/status/73700078921056256">https://twitter.com/KrokoHunter/status/73700078921056256</a></p>
<p><a href="https://twitter.com/matmannion/status/73700794624516096">https://twitter.com/matmannion/status/73700794624516096</a></p>
<p><a href="https://twitter.com/farinab/status/73700896722268160">https://twitter.com/farinab/status/73700896722268160</a></p>
<p><a href="https://twitter.com/GlennCahill87/status/73701420704075776">https://twitter.com/GlennCahill87/status/73701420704075776</a></p>
<p><a href="https://twitter.com/NIMRweb/status/73701806122868736">https://twitter.com/NIMRweb/status/73701806122868736</a></p>
<p><a href="https://twitter.com/xtinafowler/status/73702048620744704">https://twitter.com/xtinafowler/status/73702048620744704</a></p>
<p><a href="https://twitter.com/KrokoHunter/status/73728517489172480">https://twitter.com/KrokoHunter/status/73728517489172480</a></p>
<p><a href="https://twitter.com/KrokoHunter/status/74227907878334464">https://twitter.com/KrokoHunter/status/74227907878334464</a></p>
<p><a href="https://twitter.com/blagdaross/status/74429856514965504">https://twitter.com/blagdaross/status/74429856514965504</a></p>
<p><a href="https://twitter.com/sparrk/status/74556639608442880">https://twitter.com/sparrk/status/74556639608442880</a></p>
<p><a href="https://twitter.com/anttio/status/74575465905520641">https://twitter.com/anttio/status/74575465905520641</a></p>
<p><a href="https://twitter.com/sirbenjaminnunn/status/74900149448609792">https://twitter.com/sirbenjaminnunn/status/74900149448609792</a></p>
<p>You can <a href="http://talks.verou.me/css3-gradients/">play with the HTML version of my slides</a> or <a href="http://www.slideshare.net/LeaVerou/mastering-css3-gradients">view them on slideshare</a>:</p>
<p><strong><a href="http://www.slideshare.net/LeaVerou/mastering-css3-gradients" title="Mastering CSS3 gradients">Mastering CSS3 gradients</a></strong></p>
<iframe src="http://www.slideshare.net/slideshow/embed_code/8123661" width="425" height="355" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
<p>View more <a href="http://www.slideshare.net/">presentations</a> from <a href="http://www.slideshare.net/LeaVerou">Lea Verou</a></p>
<p>I really enjoyed some of the other talks in @media, especially:</p>
<ul>
<li><a href="http://www.w3conversions.com/">Stephanie Sullivan</a>’s “Modern CSS”</li>
<li><a href="http://www.brucelawson.co.uk/">Bruce Lawson</a>’s “Multimedia in HTML5”</li>
<li><a href="http://www.stubbornella.org/content/">Nicole Sullivan</a>’s “Performance of CSS3 and HTML5”</li>
</ul>
<h2 id="standards.next" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/06/my-experience-from-web-directions-media-standards-next/#standards.next">Standards.next</a></h2>
<p>The morning before my Standards.next talk, I woke up with a sore throat, a running nose and a blocked ear. I even thought about cancelling my talk, but I’m one of those people that have to be dying to change their schedule. So I went, and I’m glad I did, as I got to attend <a href="http://www.petergasston.co.uk/">Peter Gasston</a>’s incredible talk on CSS3 layout. I really learned so much stuff from that!</p>
<p>As for my talk (“CSS Secrets: 10 things you might not know about CSS3”), it went fine after all. I had some trouble hearing the questions, due to the blocked ear, but nothing too bad. I had trouble with my last demo, as I got confused and used <code>background-origin: padding-box;</code> instead of <code>content-box</code>, but nobody there hated me because of it, like I was afraid would happen if I ever screwed up one of my demos :)</p>
<p>That event was much smaller (it took place in a small room in a pub), so the tweets were much fewer, but still very positive:</p>
<p><a href="https://twitter.com/patrick%5C_h%5C_lauke/status/74496751716929536">https://twitter.com/patrick\_h\_lauke/status/74496751716929536</a></p>
<p><a href="https://twitter.com/stopsatgreen/status/74497512261693440">https://twitter.com/stopsatgreen/status/74497512261693440</a></p>
<p><a href="https://twitter.com/mfujica/status/74499137558687744">https://twitter.com/mfujica/status/74499137558687744</a></p>
<p><a href="https://twitter.com/danielknell/status/74499496746299392">https://twitter.com/danielknell/status/74499496746299392</a></p>
<p><a href="https://twitter.com/brucel/status/74500503098245121">https://twitter.com/brucel/status/74500503098245121</a></p>
<p><a href="https://twitter.com/patrick%5C_h%5C_lauke/status/74501557349138432">https://twitter.com/patrick\_h\_lauke/status/74501557349138432</a></p>
<p><a href="https://twitter.com/danielknell/status/74502234251071488">https://twitter.com/danielknell/status/74502234251071488</a></p>
<p><a href="https://twitter.com/mfujica/status/74504295927648257">https://twitter.com/mfujica/status/74504295927648257</a></p>
<p><a href="https://twitter.com/designjuju/status/74504416534855680">https://twitter.com/designjuju/status/74504416534855680</a></p>
<p><a href="https://twitter.com/mfujica/status/74504823881469952">https://twitter.com/mfujica/status/74504823881469952</a></p>
<p><a href="https://twitter.com/twitrnick/status/74506406191046656">https://twitter.com/twitrnick/status/74506406191046656</a></p>
<p><a href="https://twitter.com/patrick%5C_h%5C_lauke/status/74506910807764992">https://twitter.com/patrick\_h\_lauke/status/74506910807764992</a></p>
<p><a href="https://twitter.com/stopsatgreen/status/74508394974810113">https://twitter.com/stopsatgreen/status/74508394974810113</a></p>
<p><a href="https://twitter.com/stefsull/status/74508645513175040">https://twitter.com/stefsull/status/74508645513175040</a></p>
<p><a href="https://twitter.com/albybarber/status/74532879832584194">https://twitter.com/albybarber/status/74532879832584194</a></p>
<p><a href="https://twitter.com/jackosborne/status/74608125289828352">https://twitter.com/jackosborne/status/74608125289828352</a></p>
<p>I found out afterwards that one particular lady in the audience complained about my pronunciation of the words “fuchsia” and “ems”. That’s what I would’ve said to her if I heard: “Here’s some breaking news to you: Not everyone is a native english speaker. Shocking, isn’t it? I would really be interested to hear your pronunciation if you ever did a presentation in Greek. KKTHXBAI”</p>
<p>Overall, I had a great time in London. I hadn’t been there for more than 10 years, so I had forgotten how beautiful city it is. I loved attending and speaking at both of those events, and I would like to thank Maxine Sherrin and John Allsopp for inviting me to @media and Bruce Lawson for inviting me at Standards.next.</p>
jQuery Pure: Call for contributors2011-06-09T00:00:00Zhttps://lea.verou.me/?p=1095<p>This post is about an idea I’ve had for ages, but never found the time to actually start working on it. Maybe because it looks like a quite big project if done properly, so it’s scary to do it on my own without any help.</p>
<p>jQuery has a huge amount of code that deals with browser bugs and lack of implementations. For example, it needs a full-fledged selector engine, to cater for old browsers that don’t support the <a href="http://www.w3.org/TR/selectors-api2/">Selectors API</a>. Or, it needs code that essentially does what the classList API is supposed to do, because old browsers don’t support it. Same goes for nextElementSibling (the .next() method) and tons of other stuff. However, not everyone needs all this. Some projects don’t need older browsers support, either due to the developer mindset or due to their tech-savvy target group. Also, some people only write demos/proof-of-concepts for modern browsers only and don’t need all this code. Same goes for intranet apps that are only designed for a particular modern browser. Last but not least, this code bloat makes the jQuery library hard to study for educational purposes.</p>
<p>However, even in a browser that supports all the modern stuff, the jQuery API is still more concise than the native methods. Besides, there are tons of plugins that depend on it, so if you decide to implement everything in native JavaScript, you can’t use them.</p>
<p>What I want to build is a fork of jQuery that is refactored so that all the extra code for working around browser bugs removed and all the code replaced by native functionality, where possible. All the ugliness removed, leaving a small, concise abstraction that only uses the current standards. Something like <strong>jQuery: The good parts</strong>. It could also serve as a benchmark for browser standards support.</p>
<p>The API will work in the exact same way and pass all unit tests (in modern browsers, in cases where they are not buggy) so that almost every plugin built on top of it will continue to function just as well. However, the jQuery library itself will be much smaller, with more elegant and easy to understand code.</p>
<p><strong>So, who’s with me?</strong> Do you find such an idea interesting and useful? Would you want to contribute? If so, leave a comment below or send me an email (it’s in the <a href="http://lea.verou.me/about">about page</a>). Also, please let me know if you can think of any other uses, or if there’s already something like that that I’ve missed.</p>
Pure CSS Tic Tac Toe2011-06-17T00:00:00Zhttps://lea.verou.me/?p=1103<p>It’s supposed to be used by 2 people taking turns (click twice for the other sign).</p>
<p>Basic idea:</p>
<ul>
<li>It uses hidden checkboxes for the states (indeterminate means empty, checked means X, not checked means O) and labels for the visible part</li>
<li>When it starts, a little script (the only js in the demo) sets the states of all checkboxes to indeterminate.</li>
<li>It uses the <code>:checked</code> and <code>:indeterminate</code> pseudo-classes and sibling combinators to change the states and show who won.</li>
<li>Once somebody clicks on a checkbox (or in this case, its label) they change it’s state from indeterminate to either checked or not checked, depending on how many times they click on it.</li>
</ul>
<p><strong>As a bonus, it’s perfectly accessible through the keyboard</strong> (although I assume it’s not screen reader accessible).</p>
<p>A <code><table></code> would be much more appropriate for the markup, but I decided to sacrifice semantics in this case to make the demo simpler.</p>
<p>All modern browsers support the indeterminate state in checkboxes (for Opera you will need the latest Opera.Next), however <strong>this demo doesn’t work on old Webkit (Chrome and Safari) because of an old bug</strong> that made the sibling combinators (+ and ~) static in some cases which has been fixed in the nightlies. <strong>It should work in Firefox, Opera.next, Webkit nightlies and IE9, although I haven’t tested in Opera.next and IE9 to verify.</strong></p>
<p>Enjoy:</p>
<iframe style="width: 100%; height: 350px" src="http://jsfiddle.net/leaverou/5X5Tq/embedded/result,css,html,js"></iframe>
CSS reflections for Firefox, with -moz-element() and SVG masks2011-06-29T00:00:00Zhttps://lea.verou.me/?p=1132<p>We all know about the proprietary (and imho, horrible) <code>-webkit-box-reflect.</code> However, you can create just as flexible reflections in Firefox as well, by utilizing <a href="https://developer.mozilla.org/en/CSS/-moz-element">-moz-element()</a>, some CSS3 and Firefox’s capability to apply <a href="https://developer.mozilla.org/En/Applying_SVG_effects_to_HTML_content">SVG effects to HTML elements</a>. And all these are actually standards, so eventually, this will work in all browsers, unlike <code>-webkit-box-reflect</code>, which was never accepted by the CSS WG.</p>
<p>First and foremost, have a look at the <a href="http://lea.verou.me/demos/reflection/">demo</a>:</p>
<iframe src="http://lea.verou.me/demos/reflection/" width="100%" height="500px"></iframe>
<h2 id="how-it-works" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/06/css-reflections-for-firefox-with-moz-element-and-svg-masks/#how-it-works">How it works</a></h2>
<ul>
<li>For every element, we generate an <code>::after</code> pseudoelement with the same dimensions and a position of being right below our original element.</li>
<li>Then, we make it appear the same as our element, by giving it a background of <code>‑moz-element(#element-id)</code> and no content.</li>
<li>Reflections are flipped, so we flip it vertically, by applying <code>transform: scaleY(‑1);</code></li>
<li>If we want the reflection to have a little distance from the element (for example 10px like the demo), we also apply a transform of <code>translateY(10px)</code></li>
<li>We want the reflection to not be as opaque as the real element, so we give it an <code>opacity</code> of around 0.3-0.4</li>
<li>At this point, <strong>we already have a decent reflection</strong>, and we didn’t even need SVG masks yet. It’s essentially the same result -webkit-box-reflect gives if you don’t specify a mask image. However, to really make it look like a reflection, we apply a mask through an SVG and the <code>mask</code> CSS property. In this demo, the SVG is external, but it could be a data URI, or even embedded in the HTML.</li>
</ul>
<h2 id="caveats" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/06/css-reflections-for-firefox-with-moz-element-and-svg-masks/#caveats">Caveats</a></h2>
<ul>
<li>Won’t work with replaced elements (form controls, images etc).</li>
<li>If you have borders, it gets a bit more complicated to size it properly</li>
<li>Doesn’t degrade gracefully, you still get the pseudoelement in other browsers, so you need to filter it out yourself</li>
<li>Bad browser support (currently only Firefox 4+)</li>
<li>You need to set the reflection’s background for every element and every element needs an id to use it (but this could be done automatically via script)</li>
</ul>
<h2 id="further-reading" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/06/css-reflections-for-firefox-with-moz-element-and-svg-masks/#further-reading">Further reading</a></h2>
<ul>
<li><a href="http://www.w3.org/TR/css3-images/#element-reference">element() function</a></li>
<li><a href="http://people.mozilla.com/~roc/SVG-CSS-Effects-Draft.html">SVG effects for CSS</a></li>
<li><strong>Edit:</strong> <a href="https://hacks.mozilla.org/2010/08/mozelement/">Turns out Paul Rouget did something similar before me, back in August 2010</a>. The pros of this approach is that it works with replaced elements as well, the cons is that it requires extra markup and JavaScript.</li>
</ul>
<p>Credits: Thanks to <a href="http://wait-till-i.com/">Christian Heilmann</a> for helping me debug why SVG masks for HTML elements weren’t originally working for me.</p>
A polyfill for HTML5 progress element, the obsessive perfectionist way2011-07-02T00:00:00Zhttps://lea.verou.me/?p=1143<p><a href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/images/Screen-shot-2011-11-15-at-15.02.37-.png"><img src="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/images/Screen-shot-2011-11-15-at-15.02.37--300x219.png" alt="" title="Progress polyfill, project page screenshot" /></a>Yesterday, for some reason I don’t remember, I was looking once more at <a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills">Paul Irish’s excellent list of polyfills on Github</a>. I was really surprised to see that there are none for <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-button-element.html#the-progress-element">the <code><progress></code> element</a>. It seemed really simple: Easy to fake with CSS and only 4 IDL attributes (value, max, position and labels). “Hey, it sounds fun and easy, I’ll do it!”, I thought. I have no idea how in only 1 day this turned into “OMG, my brain is going to explode”. I’ve documented below all the pitfalls I faced. And don’t worry, it has a happy ending: I did finish it. And <a href="https://github.com/LeaVerou/HTML5-Progress-polyfill">published it</a>. So, if you’re not interested in long geeky stories, just jump straight to <a href="http://lea.verou.me/polyfills/progress/">its page</a>.</p>
<h2 id="first-things-first%3A-controlling-the-width-of-the-value-bar" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/#first-things-first%3A-controlling-the-width-of-the-value-bar">First things first: Controlling the width of the value bar</a></h2>
<p>Most progress bars out there use 2 elements: One for the container and one for the value bar. I was pretty stubborn about not using an extra element. I wanted to use pseudo-elements instead and keep the DOM tree as clean as I found it. And there it was, the first problem: How to set the width?</p>
<p>CSS3 attr() and calc() are hardly supported and <a href="http://lea.verou.me/2010/09/on-attr-and-calc/">attr() is not even allowed in calc()</a>, so I quickly realized that a pure CSS solution was out of the question. However, if I used JavaScript, how would I set a different width for every progress::before? You can’t set that in an inline style, and assigning every <code><progress></code> element an ID and adding separate rules seems a bit too intrusive to me. Think about it for a second, what would you do?</p>
<p>I realized I had to control the width of the pseudo-element through CSS properties of the parent container somehow. And then it dawned on me: If the pseudoelement has <code>display:block</code>, it will automatically get the parent width, minus the padding and borders. There it was, this was my solution. I just had to set <code>padding-right</code> accordingly, so that the value bar gets the width it needs to be! And I had already given it <code>box-sizing: border-box</code>, as it was in Webkit’s UA stylesheet, so I didn’t have to worry about padding changing the width of the element. The first problem was solved.</p>
<h2 id="becoming-dynamic" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/#becoming-dynamic">Becoming dynamic</a></h2>
<p>The static part was quite easy indeed. Selecting all <code><progress></code> elements and using their attributes to set an appropriate padding-right was pretty much run of the mill. But that wasn’t enough. What happens if you set the properties through script? What happens if you set the attributes? The progress bar should update accordingly, it had to be dynamic. A static progress bar is not much of a fallback. It might be acceptable for <code><meter></code>, since in most interfaces it’s used in a static way. But a progress bar needs to change in order to show um, <em>progress</em>.</p>
<p>First step was adding the properties that are in its DOM Interface. “Easy, I’ll add them to the prototype” thought my naïve self. So, I needed to find which prototype, I didn’t want to add them in every HTML element of course. So I eagerly typed <code>Object.prototype.toString.call(document.createElement('progress'))</code> in Firebug’s console and it slapped me in the face with an <code>'[object HTMLUnknownElement]'</code>. D’oh! I had forgotten that unknown elements share a common prototype named like that. So, I had to add them to each one individually. I hated that, but since it was the only way, I did it and moved on.</p>
<p>Of course, I didn’t just assign a static value to them, otherwise they wouldn’t solve much: The progress bar would still be static. I assigned getters and setters that used the <code>value</code> and <code>max</code> attributes to return what they should. Assigning getters and setters to a property is a whole new problem by itself, as some browsers use <code>__defineGetter__</code>/<code>__defineSetter__</code> and some others the ES5 standard <code>Object.defineProperty</code>. But <a href="http://lea.verou.me/2011/05/strongly-typed-javascript/">I had solved that one before</a>, so it didn’t slow me down.</p>
<p>The getters and setters solved the issue one-way only: If you set the properties, the progress bar and its attributes would be updated. That would be enough for most authors using the polyfill, but no, I wanted it to be <em>perfect</em>. “If you change the attributes, the progress bar and its properties should too!” my annoyingly pedantic inner self insisted. “And what if you dynamically add more <code><progress></code> elements?”.</p>
<p>There are two ways to do stuff when attributes change and elements get added: Polling and <a href="http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-eventgroupings-mutationevents">mutation events</a>. The advantage of polling is its perfect browser support, which comes at a big cost: It’s horrible performance-wise. Also, polling introduces a delay that could be unacceptable in some cases, especially considering how short the duration of some progress bar use cases is. So, I went with mutation events, even though they are deprecated (seriously W3C? deprecating something, without providing a solid alternative??) and don’t have perfect browser support. After all, it was the only way (I don’t consider polling a real option in this case).</p>
<h2 id="styling" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/#styling">Styling</a></h2>
<p>After messing around a little, it seemed to work great in Opera 10.63 and Firefox 5, which I had open for my tests. It was time to write some unit tests and check it out in more browsers. Instead, I opted to style it, as a desperate attempt to delay my confrontation with IE8 a bit longer (and for good reason, as it turned out later). Given that CSS is kinda my specialization, I expected styling to be a piece of cake and even relaxing. Instead, it came with it’s fair share of trouble and hard dilemmas.</p>
<p>If you notice the native progress bars in OSX, you will see that they use gradients. I mocked up something similar with CSS gradients, which wasn’t easy, as I wanted to keep the hue/saturation information in the background-color only, for easy modifications and Webkit uses a regular gradient with color stops that have different hues and saturations. And then I realised that this was not going to show up at all in IE8-IE9, which were 2 major browsers that my polyfill would target. No gradient may be acceptable in determinate progress bars, but it’s not an option in indeterminate ones: Scrolling diagonal stripes is the convention and there’s no other way to communicate this status to the average user.</p>
<p>So I decided to go with the old way of using raster images for gradients (through a data URI). Another painful slap in the face was when I realized that those moving stripes need to be semi-transparent. To do that, my options were:</p>
<ul>
<li>CSS3 animations - no good in my case, as it’s crucial to show up and their browser support isn’t that good</li>
<li>SVG with SMIL - Much better browser support than CSS3 animations, but still no go in IE</li>
<li>APNG - Only supported by Firefox and Opera, even after all these years</li>
</ul>
<p>I happened to be chatting with <a href="http://xanthir.com/blog">Tab Atkins</a> at the moment, and he suggested I go with plain ol’ GIFs. I was originally negative, but after thinking about it I realized that antialiasing is not that crucial in 45deg stripes, especially when they’re moving. I tried it, I liked the result, so I kept it. Phew, that one was easy.</p>
<h2 id="the-ie8-nightmare" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/#the-ie8-nightmare">The IE8 nightmare</a></h2>
<p>After spending a few hours tweaking the gradients and the CSS (yes, hours. I said I’m an obsessive perfectionist, didn’t I?) I finally wrote some unit tests and fired up Virtualbox to test with IE8. I prepared myself for the worst, and secretly hoped I’d be pleasantly surprised. Instead, I faced a developer’s worst nightmare. Two words: Stack overflow.</p>
<p>The culprit was a classic IE bug with DOM properties and HTML attrtibutes that I had blissfully forgotten: IE thinks they’re the same. I had added getters and setters (or etters, as I like to call both) to the max and value <em>properties</em> which used the max and value <em>attributes</em>, resulting in infinite recursion in IE8.</p>
<p>This was the hardest of all problems, and I never completely solved it: A few unit tests still fail in IE8 because of it, although there’s no infinite recursion any more. Luckily, this bug was fixed in IE9, so the polyfill works flawlessly there.</p>
<p>My first idea was the obvious one: to duplicate the values somewhere. In a lookup table, in another property, somewhere. I didn’t quite like the idea, so I kept brainstorming. And then it dawned on me. They’re already duplicated somewhere, and not only it’s not redundant, but actually encouraged: in the WAI-ARIA attributes!</p>
<p>To clarify, when progress elements are natively supported, they already have built-in ARIA roles and attributes. However, when they’re not, you should add them yourself, if you want the control to be accessible. From my research, there was a <code>progressbar</code> role, and it required the attributes <code>aria-valuemin</code>, <code>aria-valuemax</code>, <code>aria-valuenow</code> and <code>aria-labelledby</code>. I implemented all but the latter, as it proved too much of a hassle for very few edge cases (how many people put IDs in their <em>labels</em> without using aria-labelledby themselves?). So, <code>aria-valuemax</code> was already duplicating <code>max</code> and <code>aria-valuenow</code> was duplicating <code>value</code>. I changed everything to use those instead.</p>
<p>After lots of head-scratching, IE-cursing and feeling that my brain was going to explode all over my laptop, I managed to kinda have it working. I knew in advance that some unit tests would fail, as it doesn’t support mutation events. I eventually gave up when I realized that the last unit test in the “static” category failed because <code>getAttribute('max')</code> returned <code>null</code>, since IE had completely removed the attribute from the DOM tree. It was the last straw and made me say “That’s it, I’m done with this piece of shit”.</p>
<h2 id="safari-5-craziness" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/#safari-5-craziness">Safari 5 craziness</a></h2>
<p>After IE, it was Safari’s turn. I knew that I could only target Safari 5, as Safari 4 doesn’t support etters on DOM elements and Safari 5.1 will probably support progress elements natively, since they’ve been in Webkit for ages. I launched Safari without fear. “How can it possibly not work in Safari? It will probably be fine, maybe just need a one or two little tweaks in the worst case”, I reassured myself thinking.</p>
<p>The progress bars were not even showing. At all. My first guess was that it was a one time rendering error. When it persisted after a few reloads, I opened the dev tools to see what the hell happened. I saw a series of errors like this:</p>
<p><code><progress></code> is not allowed inside <code><label></code>. Content ignored.
Unmatched encountered. Ignoring tag.</p>
<p>At first, I thought the problem was the label. So I made all labels external. And then still got the same errors for the <code><li></code>s. And every other element I tried. Even when I put them directly into the <code><body></code>, Safari complained that they are not allowed to be inside it! It turned out that this was a bug in a build of Webkit, and coincidentally, this build was the one Safari 5 uses.</p>
<p>There wasn’t much to think about in this one: They’re not in the DOM, so I can’t do anything about them. It’s mission impossible.</p>
<h2 id="happy(%3F)-end" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/#happy(%3F)-end">Happy(?) end</a></h2>
<p>After IE8’s and Safari5’s cruel rejection, I was quite dispirited. IE8 had already caused me to make my code uglier and more verbose, and now Safari 5 flat out refuses to accept any treatment. It worked flawlessly in Firefox 3.5, but that didn’t cheer me up much. I decided that this has already taken up too much of my time. It’s now the community’s turn. Have any ideas about how further improvement? Maybe some more unit tests? I’ll be waiting for your pull requests! :) <a href="https://github.com/LeaVerou/HTML5-Progress-polyfill">Github repo</a></p>
<h2 id="appendix%3A-why-do-some-unit-tests-fail-in-browsers-that-natively-support-%3Cprogress%3E%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/#appendix%3A-why-do-some-unit-tests-fail-in-browsers-that-natively-support-%3Cprogress%3E%3F">Appendix: Why do some unit tests fail in browsers that natively support <code><progress></code>?</a></h2>
<p>While developing this, I discovered 2 browser bugs: One in Webkit’s implementation and in for Opera’s. I plan to report these soon.</p>
Detecting CSS selectors support + my JSConf EU talk2011-07-21T00:00:00Zhttps://lea.verou.me/?p=1153<p>I’ll start with a little backstory, if you want to jump straight to the meat, skip the next 4 paragraphs.</p>
<p>In the past few months, my CSS research has been getting some attention and I’ve been starting to become somewhat well-known in the CSS industry. A little known fact about me is that JavaScript has always been one of my loves, almost as much as CSS (even more than it in the past). Ironically, <a href="http://front-trends.com/">the first time I was asked to speak in a big conference</a>, it was about JavaScript, even though I ended up choosing to speak about CSS3 instead.</p>
<p>Lately, I’ve started wanting to get more into the JavaScript industry as well. I’m quite reluctant to submit speaking proposals myself (every conference or meetup I’ve given a talk so far has asked me to speak, not the other way around) and most JavaScript conferences expect you to submit a proposal yourself. I also couldn’t think of a good topic, something I was passionate about and hasn’t already been extensively covered.</p>
<p>This changed a few weeks ago. While I was writing <a href="http://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/">my <code><progress></code> polyfill</a>, it dawned on me: Polyfills is something that’s JS-related and I’m passionate about! I love studying them, writing them, talking about them. I quickly searched if there were any talks about polyfill writing already and I couldn’t find any. So, I decided to submit a proposal to <a href="http://jsconf.eu/">JSConf EU</a>, even though the call for speakers had passed 10 days ago. When I read <a href="http://twitter.com/#!/cramforce/status/90737580466896896">@cramforce’s tweet</a> that they had decided on most of the speakers, I spent a few days stressed as hell, checking my inbox every few minutes and hoping that my gut feeling that I would get accepted was right.</p>
<p>And it was! 3 days ago I received an email from JSConf EU that my proposal was accepted!! I can’t even begin to describe how happy and excited I am about it. And nervous too: What if they know everything I’m going to say? What if they hate my talk? What if the JavaScript industry is really as sexist as some people claim and they dismiss me because of my gender? I decided to put my fears aside and start working on my slides, as I couldn’t wait until later (even though I have multiple deadlines creeping up on me right now…).</p>
<p>A big part of writing polyfills is feature detection. Before trying to implement a feature with JavaScript, you first have to check if it’s already supported. So, a substantial portion of my talk will be about that. How to detect if APIs, HTML elements, CSS properties/values/selectors etc are supported. There are already established solutions and techniques about most of these, except CSS selectors. <a href="http://www.modernizr.com/">Modernizr</a> doesn’t detect any, and judging from <a href="http://www.google.com/webhp?sourceid=chrome-instant&ie=UTF-8&ion=1&nord=1#sclient=psy&hl=en&safe=off&nord=1&site=webhp&source=hp&q=detect%20css%20selector%20support&pbx=1&oq=&aq=&aqi=&aql=&gs_sm=&gs_upl=&fp=39b0a615b729321b&ion=1&ion=1&bav=on.2,or.r_gc.r_pw.&fp=39b0a615b729321b&biw=1280&bih=679&ion=1">my Google search</a> nobody has written about any techniques for doing so in a generic fashion.</p>
<p>A really simple way to detect CSS selectors support is using <code>document.querySelector()</code> in a <code>try...catch</code> statement. If the selector is not supported, an error will be thrown. However, that’s not really reliable, as the Selectors API is not supported in IE < 8. So, I thought of another idea: What if I turn the hassle of reading out a stylesheet via the DOM methods (browsers drop stuff they don’t understand) into a feature detection method?</p>
<p>The basic idea is creating a new <code><style></code> element with an empty rule and the selector we want to test support for, and then read out the stylesheet through the DOM methods to see if a rule actually exists. I’ve so far tested it in Firefox, Opera and Chrome and it seems to work. I haven’t tested it in IE yet, as I currently have too many apps running to turn on the vm, so it might need a few fixes to work there (or I might be unlucky and the idea might not work at all).</p>
<p>You can test it out yourself in this <a href="http://jsfiddle.net/leaverou/Pmn8m/">fiddle</a>, just check the console: <a href="http://fiddle.jshell.net/leaverou/Pmn8m/show/light/">http://fiddle.jshell.net/leaverou/Pmn8m/show/light/</a></p>
<p>Apologies if this has already been documented elsewhere, I really couldn’t find anything.</p>
<p><strong>Edit:</strong> <a href="http://jsfiddle.net/Skateside/5dKc7/5/">James Long worked on fixing my example’s issues with IE</a></p>
Vote for me in The .net awards 2011!2011-07-25T00:00:00Zhttps://lea.verou.me/?p=1156<p>I don’t usually post shameless plugs like that, but I’m so excited about this I decided to make an exception. A few minutes ago I found out that I’m shortlisted in the <a href="http://www.thenetawards.com/#num12">“Brilliant newcomer” category</a> of <a href="http://www.thenetawards.com/">The .net awards</a>!!!</p>
<p>Thank you so much @ everyone that nominated me and/or plans to vote for me. I really appreciate it guys*! :)</p>
<p>* “guys” in that context is used in a gender neutral fashion, I’m not only thanking the men :P</p>
My experience from the CSS Summit 20112011-08-02T00:00:00Zhttps://lea.verou.me/?p=1165<p>It’s been a few days since <a href="http://environmentsforhumans.com/2011/css-summit/">this year’s CSS Summit</a> and my talk there. Where most people would assume that public speaking in a “real” conference is more daunting, I was much more nervous about this one, since it was my first talk at an online conference. I wouldn’t be able to see the faces of the audience, so how would I be able to tell if they like it or are bored shitless? Also, the whole idea of me, alone in a room, giving a talk to my laptop sounded kind of awkward, to say the very least.</p>
<p>Contrary to my fears, it was a <strong>very</strong> pleasant experience. In some ways, it’s much better than real-life conferences, the main one being the number of questions you get. In most real-life conferences you should be lucky to get more than 3 or 4 questions. Also, they’re usually at the end, so most attendees forget the questions they had at the beginning and middle of the talk (it happens to me a lot too, when I attend others’ talks). In the CSS Summit, I answered questions after every section of my talk, and there were quite a lot of them.</p>
<p>The attendees had a group chat in which they talked about the presentation, posted questions and discussed many other stuff. That group chat was the other thing I really liked. It might surprise some people, but even though I’m not afraid of public speaking, I’m quite shy in some ways and I almost never talk to someone first. So, if I didn’t know anyone at a conference and vice versa, I’d probably sit in a corner alone with nobody to talk to during the breaks. The chat makes it much easier for attendees to get to know each other. On the minus side however, “meeting” somebody in a chat is not by any means the same as really meeting someone f2f in a real-life conference.</p>
<p>Regarding my talk, it went surprisingly well. No technical hiccups like some of the other talks, no me going overtime as I was afraid I would (since I had to be slower), no internet connection failing on my part (like it sometimes does lately). I received lots of enthusiastic feedback on both the chat and twitter. I couldn’t even favorite them all, as the tweets were so many! That’s the 3rd good thing about online conferences: People tweet more, since they’re at home with their regular connection and not with a crappy conference wifi or a smartphone on expensive roaming.</p>
<p>Here’s a small sample of the feedback I got:</p>
<p><a href="https://twitter.com/teleject/status/95872896257372161">https://twitter.com/teleject/status/95872896257372161</a></p>
<p><a href="https://twitter.com/boblet/status/95874502608691201">https://twitter.com/boblet/status/95874502608691201</a></p>
<p><a href="https://twitter.com/chriscoyier/status/95875236511236096">https://twitter.com/chriscoyier/status/95875236511236096</a></p>
<p><a href="https://twitter.com/snookca/status/95875859579277312">https://twitter.com/snookca/status/95875859579277312</a></p>
<p><a href="https://twitter.com/elizabethyalkut/status/95876076647100416">https://twitter.com/elizabethyalkut/status/95876076647100416</a></p>
<p><a href="https://twitter.com/wloescher/status/95877653730557952">https://twitter.com/wloescher/status/95877653730557952</a></p>
<p><a href="https://twitter.com/martuishere/status/95878117872246784">https://twitter.com/martuishere/status/95878117872246784</a></p>
<p><a href="https://twitter.com/PetraGregorova/status/95878976446271488">https://twitter.com/PetraGregorova/status/95878976446271488</a></p>
<p><a href="https://twitter.com/caffeinerush/status/95879094406873088">https://twitter.com/caffeinerush/status/95879094406873088</a></p>
<p><a href="https://twitter.com/yodasw16/status/95879645743951872">https://twitter.com/yodasw16/status/95879645743951872</a></p>
<p><a href="https://twitter.com/matthewcarleton/status/95880868744278017">https://twitter.com/matthewcarleton/status/95880868744278017</a></p>
<p><a href="https://twitter.com/redcrew/status/95881025602863104">https://twitter.com/redcrew/status/95881025602863104</a></p>
<p><a href="https://twitter.com/brandongcarroll/status/95881316796600320">https://twitter.com/brandongcarroll/status/95881316796600320</a></p>
<p><a href="https://twitter.com/digitalCULT/status/95884698315792384">https://twitter.com/digitalCULT/status/95884698315792384</a></p>
<p><a href="https://twitter.com/candiRSX/status/95885394192769025">https://twitter.com/candiRSX/status/95885394192769025</a></p>
<p><a href="https://twitter.com/jewlofthelotus/status/95885472802422784">https://twitter.com/jewlofthelotus/status/95885472802422784</a></p>
<p><a href="https://twitter.com/mortendk/status/95885749404176384">https://twitter.com/mortendk/status/95885749404176384</a></p>
<p><a href="https://twitter.com/idoclosecuts/status/95886200342188032">https://twitter.com/idoclosecuts/status/95886200342188032</a></p>
<p><a href="https://twitter.com/megcarpen/status/95886211897495552">https://twitter.com/megcarpen/status/95886211897495552</a></p>
<p><a href="https://twitter.com/V%5C_v%5C_V/status/95886299902386176">https://twitter.com/V\_v\_V/status/95886299902386176</a></p>
<p><a href="https://twitter.com/iScreem/status/95886472934195200">https://twitter.com/iScreem/status/95886472934195200</a></p>
<p><a href="https://twitter.com/pixelfuture/status/95886741176717312">https://twitter.com/pixelfuture/status/95886741176717312</a></p>
<p><a href="https://twitter.com/suprMax/status/95887286369132544">https://twitter.com/suprMax/status/95887286369132544</a></p>
<p><a href="https://twitter.com/PetraGregorova/status/95899659159080960">https://twitter.com/PetraGregorova/status/95899659159080960</a></p>
<p>Deborah Edwards-Onoro also put together a <a href="http://deboraheo.posterous.com/css-summit-2011-css3-and-gradients-by-lea-ver">write-up</a> for some parts of my presentation.</p>
<p>Thank you all!!</p>
CSS3 for developers: My Fronteers 2011 workshop2011-08-06T00:00:00Zhttps://lea.verou.me/?p=1170<p>In case you haven’t noticed, in addition to my talk at <a href="http://fronteers.nl/congres/2011">Fronteers 2011</a>, I’ll also be holding <a href="http://fronteers.nl/congres/2011/workshops/css3-for-web-developers-lea-verou">a full day workshop</a> the day before the conference. The title of that workshop is “<a href="http://fronteers.nl/congres/2011/workshops/css3-for-web-developers-lea-verou">CSS3 for developers</a>” and I wanted to explain a bit what it’s going to be about and why I chose to target web developers only.</p>
<h3 id="why-%E2%80%9Cfor-developers%E2%80%9D%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/css3-for-developers-my-fronteers-2011-workshop/#why-%E2%80%9Cfor-developers%E2%80%9D%3F">Why “for developers”?</a></h3>
<p>First of all, even though I do design websites and I really love design (not only web design), I consider myself primarily a developer. So, I think I can communicate better with other devs, rather than designers, since we “speak the same language”. Secondly, most CSS3 talks and workshops are presented by and targeted to, designers. Developers end up feeling left out and in return they tend to consider CSS an inferior technology which isn’t for them. CSS might not be a programming language, but it <strong>is</strong> code, and to fully master, it requires a very similar skillset to programming. It’s no wonder that most people that actually do research on CSS and/or write the specifications are <strong>not</strong> designers.</p>
<p>Besides, CSS3, in essence, is about creating web applications that download faster and are easier to develop, maintain and edit. There are very few things that can’t be done at all with CSS2.1. CSS3 just allows us to do them better: Less HTTP requests, less kilobytes to download, less presentational JavaScript, more flexibility. CSS3 is mostly about coding speed, flexibility, performance, maintainability. None of these are artistic pursuits, they’re all purely developer goals!</p>
<h3 id="what-will-it-be-about%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/css3-for-developers-my-fronteers-2011-workshop/#what-will-it-be-about%3F">What will it be about?</a></h3>
<p>It will be about many well-implemented and popular CSS3 features, like border-radius, shadows, gradients, new background properties, selectors, media queries, transforms, transitions etc. The key difference from most CSS3 talks & workshops will be the <strong>depth</strong> these will be covered in and the <strong>different perspective</strong> (practical information rather than artistic or “inspirational”). I usually opt in for depth rather than breadth for my talks, and expect the same from this workshop. The feedback I get most frequently for my talks is “I thought I knew everything about topic X, but yet I learned so much!”. I’ll do my best to maintain this reputation for this workshop as well. ;)</p>
<p>In addition to learning how CSS3 stuff can be used, information about browser support, fallbacks and performance will be provided.</p>
<h3 id="watch-a-single-person-talk-for-a-whole-day%3F-boooooring!" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/css3-for-developers-my-fronteers-2011-workshop/#watch-a-single-person-talk-for-a-whole-day%3F-boooooring!">Watch a single person talk for a whole day? Boooooring!</a></h3>
<p>Hey, I agree. And it’s not just that: Listening to someone talk about a given topic and trying it out yourself are two very different things. I believe that you only really learn something when you actually use it. That’s why it won’t be done like that. ;) It will be very hands on and there is going to be at least one small exercise per almost everything explained. The exercises are going to be performed in a little web app designed exclusively for this workshop, so that you won’t need to bother with prefixes or write HTML and irrelevant CSS code just to try out a new property. And what’s best, you can take that app at home with you and practice what you learned as much as you want!</p>
<p>Of course that means that every attendee will have to bring their own laptop (or borrow one from a friend).</p>
<h3 id="kewl%2C-can-i-haz-ticket%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/css3-for-developers-my-fronteers-2011-workshop/#kewl%2C-can-i-haz-ticket%3F">Kewl, can I haz ticket?</a></h3>
<p>Workshop attendance is priced at €350 (€275 for Fronteers members) and you can get your ticket here: <a href="http://fronteers.paydro.net/">http://fronteers.paydro.net/</a></p>
<p>To make the experience better and more educational, we limited the number of attendees to 30. That might mean you need to rush: there are currently tickets available, but in a few days there might not be any left!</p>
<p>Looking forward to seeing you in Amsterdam! :)</p>
CSS gradients are faster than SVG backgrounds2011-08-12T00:00:00Zhttps://lea.verou.me/?p=1188<p>Which is really sad, because SVG is awesome. It lets you do what CSS gradients do and much more, in quite a small filesize, as it’s just text too. However, the browser needs to generate a DOM for every SVG graphic, which results in sluggishness.</p>
<p><a href="http://jsfiddle.net/leaverou/8hQEy/embedded/result%2Ccss%2Cjs/">Here’s my test case</a></p>
<p>Mouse over the 2 divs. They both use a spotlight effect that’s dynamically updated according to the position of the mouse cursor. One of them does it with an SVG (through a data URI), the other one through a CSS radial gradient.</p>
<p>The test only works in <strong>Chrome</strong>, <strong>Firefox nightlies</strong> and <strong>perhaps IE10</strong> (haven’t tested in Windows). Why? Because <strong>Opera</strong> doesn’t support radial gradients yet (however you can see how slow SVG is in it too), and <strong>Firefox</strong> before the nightlies used to have <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=308590">a bug with gradients in SVG through data URIs</a>. Also, jsFiddle seems not to work in <strong>Webkit nightlies</strong> for some reason, but I’m too lazy right now to make a self-hosted test case.</p>
<p>Thanks a lot to <a href="http://twitter.com/#!/__chris__">Christian Krebs</a> (lead developer of Opera Dragonfly) who inspired these tests after a discussion we had today regarding CSS gradient performance.</p>
<p>Edit: According to some commenters, they’re the same speed on Windows and Linux, so it could be an OSX issue. The only way to know for sure is to post more results, so go ahead and post yours!</p>
<p>Also, some commenters say that this is not a fair comparison, because it generates a new SVG every time. I have several arguments to reply to this:</p>
<ol>
<li>We also generate a new gradient every time, so it is fair.</li>
<li>You can’t manipulate an SVG used for a background, so it’s not an option for backgrounds. JS doesn’t run in it and you don’t have access to its DOM. The only way to do that would be to use an inline SVG embedded in HTML and the element() CSS3 function. However, that’s only supported by Firefox, so not really a pragmatic option.</li>
</ol>
twee+: Longer tweets, no strings attached2011-08-14T00:00:00Zhttps://lea.verou.me/?p=1197<p><img src="https://lea.verou.me/2011/08/tweeplus-longer-tweets-no-strings-attached/images/tweeplus-300x200.png" alt="" title="tweeplus screenshot" />As some people that have been following me for a while know, the 140 character limit on twitter bugs me a lot sometimes, and I’ve tried to find <a href="http://lea.verou.me/2009/11/exploring-browser-supported-unicode-characters-and-a-tweet-shortening-experiment/">a way to overcome it</a> previously as well. The most common ways these days seems to be either cutting down the long tweet into multiple pieces (yuck) or using a service to host the longer tweet and post a summary with a link to it.</p>
<p>The latter isn’t an entirely horrible option. However, I see 3 big downsides:</p>
<ol>
<li>I’m not very comfortable with the idea of some external service hosting my content which could close down any time due to failure to monetize their website. In that case, I’d be left with some dead links that are of no value and my content would be lost forever. Yes, they usually warn you in advance in such cases, but such news could be missed for a number of reasons.</li>
<li>People (including yours truly) don’t plan those things in advance. They just seek services like that at the exact moment when they want to post a long tweet. Being greeted with a prompt to use Twitter Connect is NOT nice. For starters, it slows me down. Also, I don’t want to give permission to every website on the effing interwebs to post on my twitter timeline. I owe it to my followers to be responsible and not risk filling their timelines with crap.</li>
<li>Most of these websites look like someone puked and what came out happened to be HTML and CSS. The only exception I’ve found is <a href="http://twtmore.com/">twtmore</a>, but it still suffers from #1 and #2.</li>
</ol>
<p>So, like every developer with a healthy amount of NIH syndrome, I decided to write my own :D</p>
<p>My goals were:</p>
<ol>
<li>It had to be entirely client-side (except initially getting downloaded from the server of course). This way, whoever is concerned can download the full website and decode their tweets with it if it ever goes down. Also, being entirely client side allows it to scale very easily, as serving files is not a very resource intensive job (compared to databases and the like).</li>
<li>No Twitter Connect, the tweets would get posted through <a href="https://dev.twitter.com/docs/intents">Twitter Web Intents</a>.</li>
<li>It had to look good. I’m not primarily a designer, so I can’t make something that looks jaw-droppingly amazing, but I can at least make it look very decent.</li>
<li>If I was gonna go through all the hassle of making this, I may as well try to keep it under 10K, so that I could take part in the <a href="http://10k.aneventapart.com/">10K apart contest</a>. (I haven’t submitted it yet, I’ll submit a few days before the deadline, as it seems you can’t make changes to your submission and I want to polish the code a bit, especially the JS — I’m not too proud about it)</li>
</ol>
<p>I managed to succeed in all my goals and I really liked the result. I ended up using it for stuff I never imagined, like checking if a twitter username corresponds to the account I think (as it shows the avatars). So I went ahead and came up with a name, bought a domain for it, and <a href="http://tweeplus.com/">tweeplus.com</a> was born :)</p>
<h2 id="twee%2B%3F-seriously%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/tweeplus-longer-tweets-no-strings-attached/#twee%2B%3F-seriously%3F">twee+? Seriously?</a></h2>
<p>Yes, I like it. The plus means “more”, which is fitting and + kinda looks like a t, so it could be read as “tweet” as well. Yes, I know that the word “twee” has some negative connotations, but oh well, I still like the name. Whoever doesn’t can just not use it, I won’t get depressed, I promise. :P</p>
<h2 id="geeky-stuff" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/tweeplus-longer-tweets-no-strings-attached/#geeky-stuff">Geeky stuff</a></h2>
<h3 id="how-it-works" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/tweeplus-longer-tweets-no-strings-attached/#how-it-works">How it works</a></h3>
<ul>
<li>A relatively new feature, Twitter automatically wraps URLs in <a href="http://t.co/">t.co</a> links, making them only 20 characters long.</li>
<li>All the text of the tweet is stored in the URL hash (query string will also work, although the output uses a hash). Some research revealed that actually <a href="http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url">browsers can handle surprisingly long URLs</a>. Essentially, the only limit (2083 characters) is enforced by Internet Explorer. I decided to limit tweets to 2000 characters (encoded length), not only because of the IE limit, but also because I wouldn’t like people to post whole books in <a href="http://t.co/">t.co</a> links. We don’t want Twitter to start taking measures against this, do we? :)</li>
<li>A hard part was deciding which encoding to use (twitter is quite limited in the characters it parses as part of a URL).
<ul>
<li>My first thought was base64, but I quickly realized this was not a very good idea:
<ul>
<li>The encoding & decoding functions (<code>btoa()</code> and <code>atob()</code> respectively) are relatively new and therefore not supported by older browsers. I’m fine with the app hardly working in old browsers, but existing links must as a minimum be readable.</li>
<li>It uses approximately 1.34 characters per ASCII character. Unicode characters need to be URL-encoded first, otherwise an Exception is thrown. URL-encoding them uses 6 characters, which would result in 8 characters when they’re base64 encoded.</li>
</ul>
</li>
<li>Then I thought of using URL-encoding for the whole thing. The good thing with it is that for latin alphanumeric characters (which are the most) it doesn’t increase the string length at all. For non-alphanumeric characters it takes up 3 characters and 6 characters for Unicode ones. Also, it’s much more readable.</li>
<li>Still, implementing it was tricky. It doesn’t encode some characters (like the dot), even though twitter doesn’t accept them as part of a URL. Also, escape() and encodeURI() behave differently and <a href="http://twitter.com/#!/LeaVerou/status/101348203756130304">the Unicode encoding returned by the former isn’t accepted by Twitter</a>. So I had to combine the two and do some substitutions manually.</li>
</ul>
</li>
<li>When the textarea changes, the hash does too. The whole thing is a form with action=“<a href="http://twitter.com/intent/tweet">http://twitter.com/intent/tweet</a>”, so submitting it does the right thing naturally. I keep a hidden input with the tweet and the textarea has no name, so it doesn’t get submitted.</li>
<li>Usernames, hashtags and URLs get extracted and linkified. Usernames also get an avatar (it’s dead easy: Just use <a href="http://twitter.com/api/users/profile">twitter.com/api/users/profile</a>_image?screen_name={username} where {username} is the user’s username)</li>
<li>Internal “pages” (like “About” or “Browser support”) are essentially long “tweets” too.</li>
<li>A little easter egg is that if the browser supports radial gradients, the gradient follows the mouse, creating a spotlight effect. This looks nice on Chrome and Firefox, and really shitty on IE10, probably due to bugs in the gradient implementation (which I have to reduce & report sometime).</li>
</ul>
<h3 id="buzzword-compliance" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/08/tweeplus-longer-tweets-no-strings-attached/#buzzword-compliance">Buzzword compliance</a></h3>
<p>This little app demonstrates quite a lot new open web technologies (HTML5, CSS3 etc), such as:</p>
<ul>
<li>textarea maxlength</li>
<li>placeholder</li>
<li>autofocus (edit: I had to remove it cause it triggered a Webkit bug in Safari)</li>
<li>required inputs</li>
<li>New input types (url)</li>
<li>History API</li>
<li>oninput event (with <a href="http://mathiasbynens.be/notes/oninput">keyup fallback</a>)</li>
<li>hashchange event</li>
<li>SVG</li>
<li>Common CSS3 (border-radius, shadows, transitions, rgba, media queries)</li>
<li>CSS3 gradients</li>
<li>CSS3 animations</li>
<li>CSS3 resize</li>
<li>:empty</li>
</ul>
<p>Let me know if I forgot something.</p>
<p>Oh yeah, I did forget something. There it is: <a href="http://tweeplus.com/">twee+</a></p>
Better “CSS3 ticket-like tags”2011-08-15T00:00:00Zhttps://lea.verou.me/?p=1210<p>Today I stumbled upon <a href="http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/quick-tip-create-pure-css3-ticket-like-tags/">this</a> tutorial, which from the screenshot, looked very interesting. So, I read on, and to my horror I noticed the author suggesting some questionable practices, the worst of which was using 3 HTML elements for every tag, including nonsense markup like <code><span class="circle"></span></code>.</p>
<p>So, I thought I’d take a chance at trying to recreate this effect without any extra markup. And it turned out to be quite easy, although using CSS gradients limits browser support a bit (IE10, Firefox 3.6+, Chrome, Safari 5.1).</p>
<p>They have the same disadvantage as the originals: They depend on the background color. However, unlike the originals, they come at less code, they’re scalable without changing a million values (as shown in the “bigger” section) and of course, no extra markup.</p>
<p>You can see the results in <a href="http://jsfiddle.net/leaverou/T9bmw/">the fiddle</a> below:</p>
<iframe style="width: 100%; height: 400px" src="https://jsfiddle.net/leaverou/T9bmw/embedded/result%2Ccss%2Chtml"></iframe>
<p>Disclaimer: <a href="http://webdesign.tutsplus.com/">webdesign tuts+</a> occasionally has some nice tutorials. I didn’t write this post to attack them in any way.</p>
Why I love our industry2011-08-17T00:00:00Zhttps://lea.verou.me/?p=1225<p>I was thinking today how blessed I feel for being a part of the worldwide web development community (and the broader programming community). In a world where throwing shit at others is an acceptable way of climbing to the top, our industry is a breeze of fresh air. Here are a few reasons why I find our industry unique, in a very good way:</p>
<ul>
<li>In which other industry is it common for people to spend several hours, days or in some cases even months, working on something to give it away for free, just to help people?</li>
<li>In which other industry do people help you and promote you just because they think you’re good, without getting anything out of it?</li>
<li>In which other industry do people listen to you, not because of your titles, degrees and “decades of experience”, but because of what you actually know?</li>
<li>In which other industry can you go to a big professional conference with jeans and a t-shirt and be in the majority? (And the best part is, even if you don’t like that kind of outfit and you prefer to wear a suit, you still fit in, cause appearances just don’t matter)</li>
<li>Judging whether someone’s work is good is a very rational and objective process (unlike arts). Sure, the various criteria have different weights for every person, but the criteria are the same for everyone, more or less (correctness, speed, maintainability, readability etc).</li>
<li>Even though it’s a male dominated field, I’ve never* experienced any discrimination or lack of respect due to my gender. Quite the contrary actually.</li>
<li>I’ve yet to meet a developer that lacks a sense of humor.</li>
<li>Work for us is our passion, not a chore. Yes, there are passionate people in every field, but in our industry it’s the norm, not the exception.</li>
<li>You don’t need to hide your geekiness. Instead, you’re encouraged to embrace it.</li>
</ul>
<p>Yes, I know that not all of them are true for every single person that happened to be a web developer. I’m talking about the part of the industry that’s active and that I meet in conferences, meetups, twitter etc.</p>
<p>So, what are <strong>your</strong> reasons for liking our industry, if any? Lets keep this post happy and not whine about what we DON’T like please. :)</p>
<p>*Well, except one bad joke once, but he recently said he’s sorry and his intentions were good throughout, so I don’t count it.</p>
Accessible star rating widget with pure CSS2011-08-18T00:00:00Zhttps://lea.verou.me/?p=1233<p>For ages, we couldn’t utilize the sibling combinators (<code>~</code> and <code>+</code>) to ease the pain of creating star rating widgets, because of <a href="http://css-tricks.com/8439-webkit-sibling-bug/">this stupid Webkit bug</a>. Nowadays, not only it’s fixed, but the fix has already propagated to Chrome and Safari 5.1. So, we can at least use the sibling combinator to make coloring the stars easier.</p>
<p>But can we use <strong>no JavaScript</strong> for a rating widget and make it just with CSS?</p>
<p>Actually, we can. By adapting <a href="http://www.thecssninja.com/css/custom-inputs-using-css">Ryan Seddon’s technique for custom radio buttons with CSS</a>, we can turn a series of radio buttons into stars that change colors (for the purposes of this demo they’re just unicode characters that change colors, but in your case they may as well be images) and use the sibling combinator to color the previous stars. <a href="http://twitter.com/#!/stephband/status/104159169657053184">A</a> <a href="http://twitter.com/#!/scottjehl/status/104194465480183808">series</a> <a href="http://twitter.com/#!/anselmhannemann/status/104176613159206912">of</a> <a href="http://twitter.com/#!/hlb/status/104176520939044865">radio</a> <a href="http://twitter.com/#!/tomfullerton/status/104165058191433728">buttons</a> <a href="http://twitter.com/#!/leads/status/104161288279695360">is</a> <a href="http://twitter.com/#!/jamygolden/status/104158932267827201">what</a> <a href="http://twitter.com/#!/thijs/status/104158812684038144">many</a> <a href="http://twitter.com/#!/rossbruniges/status/104157949064249344">people</a> <a href="http://twitter.com/#!/maskingtape/status/104157878230843392">use</a> <a href="http://twitter.com/#!/edge0703/status/104157335756341249">as</a> <a href="http://twitter.com/#!/rasmusfl0e/status/104157216029949955">a</a> <a href="http://twitter.com/#!/stephenhay/status/104157128704540672">star</a> <a href="http://twitter.com/#!/derSchepp/status/104157124787060737">rating</a> <a href="http://twitter.com/#!/hzr/status/104160608848584704">widget</a> <a href="http://twitter.com/#!/iPaintCode/status/104161792925765633">fallback</a> anyway, so the markup required is not necessarily more than usual. The only thing that needs to be done differently is their reverse ordering: The highest ratings need to go first, due to the way CSS3 selectors work (this limitation might be removed in CSS4, but that’s a long way ahead).</p>
<p>Of course, you’d still need JS to attach an event handler if you want the votes to be registered through AJAX, but that’s not part of the rating widget per se (it could still work as part of a regular form).</p>
<p>What’s best is that it’s fully keyboard accessible (focus and then use keyboard arrows) and screen reader accessible (although VoiceOver will also pronounce the generated stars, but that won’t happen if you use images instead of unicode stars). I’m guessing it could become even more accessible with proper ARIA, but I’ll leave that as an exercise to the commenter :D</p>
<p>In browsers that don’t support <code>:checked</code> (essentially only IE < 9), it degrades to a series of radio buttons (haven’t verified that it does, but it should do).</p>
<p>So, here it is:</p>
<iframe style="width: 100%; height: 180px;" src="http://jsfiddle.net/leaverou/CGP87/embedded/result%2Ccss%2Chtml/" width="320" height="240"></iframe>
<p>Legal note, for those who need it: This code is MIT licensed.</p>
To write good code, you sometimes have to write bad code2011-08-20T00:00:00Zhttps://lea.verou.me/?p=1242<p>And I’m not referring to learning.</p>
<p>For example, yesterday I was trying to write code for something and it ended up beng harder than I expected. It’s one of those rare cases where you can fully imagine how the solution should work, enough to tell it to another person, but you can’t put your thoughts to code and you feel you’re not smart enough.</p>
<p>I find that in those cases, it helps a lot to open a new editor window and try to write code that just works. Without being elegant, fast or maintainable. Just something that works properly. And after you manage to put your thoughts into (bad) code, it’s easy to refine it from there and end up with good code.</p>
<p>Just don’t stop at the bad code, like many beginners do. It’s like when designers sketch a rough draft for a logo, before drawing the digital version. Could you imagine how horrible it would be if they wanted to stop there and give the rough sketches to the client instead? :)</p>
On URL readability2011-08-30T00:00:00Zhttps://lea.verou.me/?p=1251<p>Yesterday, I was watching some season 6 episodes of Futurama (btw, this is their best season ever!) and I noticed the URLs in the website I was in (let’s call it <a href="http://foo.com/">foo.com</a>). They were like:</p>
<p><a href="http://foo.com/futurama/season/6/episode/9">http://foo.com/futurama/season/6/episode/9</a></p>
<p>I thought to myself “hey, this looks very clean and readable”. And then I noticed that it only has 1 less character than its non-rewritten counterpart:</p>
<p><a href="http://foo.com/?futurama&season=6&episode=9">http://foo.com/?futurama&season=6&episode=9</a></p>
<p>However, I’m pretty sure you agree that the second one is much harder to read. I <a href="http://twitter.com/#!/LeaVerou/status/108356094467915776">asked for opinions on twitter</a>, and got many interesting replies. Apart from the ones that completely missed the point, these were the core explanations:</p>
<ul>
<li>= and especially & are more complex and look more like letters, so our brain has trouble tuning them out (<a href="http://twitter.com/#!/feather/status/108358461888270337">@feather</a> <a href="http://twitter.com/#!/robert_tilt/status/108410782777229312">@robert_tilt</a> <a href="http://twitter.com/#!/rexxars/status/108427976768622592">@rexxars</a> <a href="http://twitter.com/#!/mrtazz/status/108431965241360384">@mrtazz</a> <a href="http://twitter.com/#!/manchurian/status/108545434011705344">@manchurian</a>)</li>
<li>Slashes have more whitespace around them, so they are less obtrusive (<a href="http://twitter.com/#!/feather/status/108357826916794368">@feather</a> <a href="http://twitter.com/#!/stevelove/status/108393480488878080">@stevelove</a> <a href="http://twitter.com/#!/kenny1987/status/108415712292388864">@kenny1987</a> <a href="http://twitter.com/#!/janl/status/108432181549989888">@janl</a>)</li>
<li>They’re all visual noise, but we always have slashes in a URL, so using the slash to separate keys and values as well only introduces 1 separator instead of 3 (<a href="http://twitter.com/#!/Bugster/status/108356318741528577">@bugster</a> <a href="http://twitter.com/#!/craigpatik/status/108360564924874752">@craigpatik</a> <a href="http://twitter.com/#!/nyaray/status/108409202522861568">@nyaray</a>)</li>
<li>Slashes imply hierarchy, which our brains process easier than key-value pairs. Key-value pairs could be in any order, paths have a specified order. (<a href="http://twitter.com/#!/sggottlieb/status/108356697109700609">@sggottlieb</a> <a href="http://twitter.com/#!/edwelker/status/108357422292283393">@edwelker</a> <a href="http://twitter.com/#!/stephenhay/status/108410752469180417">@stevenhay</a> <a href="http://twitter.com/#!/jwajsberg/status/108420275258916864">@jwasjsberg</a> <a href="http://twitter.com/#!/stazybohorn/status/108518754878623744">@stazybohorn</a>)</li>
<li>Ampersands and equal signs are harder to type than slashes. They’re both in the top row and ampersands even need the Shift key as well. (<a href="http://twitter.com/#!/feather/status/108358640787927040">@feather</a>)</li>
<li>Ampersands and equal signs have semantic meaning in our minds, whereas slashes not as much (<a href="http://twitter.com/#!/snadon/status/108394403164467200">@snadon</a>)</li>
</ul>
<p>Regarding hierarchy and RESTful design, the first example isn’t exactly correct. If it was hierarchical, it should be <a href="http://foo.com/futurama/season">foo.com/futurama/season</a><strong>s</strong>/6/episode<strong>s</strong>/9. As it currently stands, it’s key-value pairs, masquerading as hierarchical. However, it still reads better.</p>
<p>So I’m leaning towards the first three explanations, although I think all of them have a grain of truth. Which makes me wonder: Did we choose the wrong characters for our protocol? Could we have saved ourselves the hassle and performance overhead of URL rewriting if we were a bit more careful in choosing the separators back then?</p>
<p>Also, some food for thought: Where do you think the following URLs stand in the legibility scale?</p>
<p><a href="http://foo.com/futurama/season=6/episode=9">http://foo.com/futurama/season=6/episode=9</a></p>
<p><a href="http://foo.com/futurama/season:6/episode:9">http://foo.com/futurama/season:6/episode:9</a></p>
<p>http : <a href="https://foo.com/futurama-season-6-episode-9">//foo.com/futurama-season-6-episode-9</a> (suggested by Ben Alman)</p>
<p>Do you think there are there any explanations that I missed?</p>
CSS.coloratum: Convert and share CSS colors2011-09-02T00:00:00Zhttps://lea.verou.me/?p=1257<p><img src="https://lea.verou.me/2011/09/css-coloratum-convert-and-share-css-colors/images/shot1-300x200.png" alt="" title="screenshot" />Whenever I wanted to convert a CSS named color to RGB, I used to open <a href="http://www.w3.org/TR/css3-color/">the CSS3 colors spec</a> in a new tab, search in the page and copied the values. Every time it felt even more tedious. I didn’t want to search in long tables, I wanted to type the color somewhere and get the values back, in an easy to copy format. So, after yet another color lookup earlier today, I decided to scratch my own itch and do it myself.</p>
<p>Of course, I didn’t plan to include a whole database of CSS colors in the website. My idea was much simpler: Use the named color to draw a rectangle in a <code><canvas></code> and then read the R,G,B values through ctx.getImageData().</p>
<p>I got the core functionality done in under 10 minutes, so I started adding stuff. I added a hex and HSL representation, I used canvas.toDataURL() to get a data URI of the rectangle and use it as a dynamic favicon*, I made the colors sharable and bookmarkable by using an old-fashioned hash. Also, I realized it actually supports any CSS supported color represenation by design, not just named colors.</p>
<p>Regarding the color conversions themselves, I took extra care to avoid redundancy. So values < 1 don’t have leading zeroes (.5 instead of 0.5) and when the hex color is in the format #xxyyzz it gets converted to #xyz. When it’s an RGBA color, it still converts it to hex, since those values will be supported in CSS4.</p>
<p>Since it’s for developers, I didn’t bother at all with fallbacks.</p>
<p>Cool stuff used:</p>
<ul>
<li>HTML5: canvas, autofocus, output, oninput event, hashchange event</li>
<li>CSS3: gradients, media queries, box-sizing, background-clip, border-radius, shadows, RGBA</li>
<li>ES5: Array#map()</li>
<li>Selectors API</li>
</ul>
<p>The reason the input’s border appears weird on Webkit is <a href="https://bugs.webkit.org/show_bug.cgi?id=63952">this long standing Webkit bug</a>. Also, for some reason my nice dynamic favicons don’t display on Firefox, although they display fine in Webkit and Opera.</p>
<p>Enjoy: <a href="http://css.coloratum.com/">CSS.coloratum</a></p>
<p>Happy color sharing! Let me know of any problems or suggestions you may have.</p>
<p>PS: In case you’re wondering about the domain, I’ve had it for ages for another project and I thought it was quite fitting.</p>
<p>*Thanks to <a href="http://twitter.com/milo">@milo</a> for giving me the idea of using a dynamic favicon</p>
Pure CSS3 typing animation with steps()2011-09-05T00:00:00Zhttps://lea.verou.me/?p=1269<p><a href="http://dev.w3.org/csswg/css3-animations/#animation-timing-function">steps()</a> is a relatively new addition to the CSS3 animations module. Instead of interpolating the values smoothly, it allows us to define the number of “frames” precisely. So I used it to create headers that have the well-known animated “typing effect”:</p>
<iframe style="width: 100%; height: 160px" src="http://jsfiddle.net/leaverou/7rnQP/embedded/result%2Ccss%2Chtml"></iframe>
<p>As you can see, the number of characters is hardcoded in the steps() function, but that’s the only place. Everything else is totally flexible. Apart from the font: It has to be monospace, so that every character has the same width.</p>
<p>Also, this particular way requires a solid background and an extra <code><span></code>. You can avoid these limitations by directly animating the width of the heading itself, but this requires a fixed target width hardcoded in the animation, so 2 things that need to be changed for every heading:</p>
<iframe style="width: 100%; height: 160px" src="http://jsfiddle.net/leaverou/y8kNx/embedded/result%2Ccss%2Chtml"></iframe>
<p>If you’re having trouble understanding how it works, take a look at <a href="http://jsfiddle.net/leaverou/vrEnp/">this simpler example</a>, with just the cursor.</p>
<p>Gecko (Firefox) and Webkit only at the moment, since other engines haven’t implemented CSS animations yet. However, both examples degrade very gracefully in other browsers (IMO at least).</p>
Help the community: report browser bugs2011-09-07T00:00:00Zhttps://lea.verou.me/?p=1282<p>Thought I’d let you know that <a href="http://coding.smashingmagazine.com/2011/09/07/help-the-community-report-browser-bugs/comment-page-1/">my Smashing Magazine article with that title</a> was published today. It discusses why, how, when and where to report browser bugs, as well as how to make a good bug report.</p>
<p>Get comfortable and make a big cup of coffee before you dive in, as it’s quite long (4000 words).</p>
Chainvas: Make APIs chainable, enhance the canvas API2011-09-11T00:00:00Zhttps://lea.verou.me/?p=1288<p><a href="https://lea.verou.me/2011/09/chainvas-make-apis-chainable-enhance-the-canvas-api/images/chainvas.png"><img src="https://lea.verou.me/2011/09/chainvas-make-apis-chainable-enhance-the-canvas-api/images/chainvas-300x228.png" alt="" title="chainvas page screenshot" /></a>It’s definitely not the first time someone writes a script to make the canvas API chainable, as <a href="http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=chainable+canvas">a quick Google search</a> will confirm.</p>
<p>However, I think my attempt has merit, because it’s not really focused in chaining canvas methods, but just about every API you use it on and because it’s super small, only 1KB!</p>
<p>You can find it here: <a href="http://lea.verou.me/chainvas/">chainvas</a></p>
<p>Enjoy!</p>
A better tool for cubic-bezier() easing2011-09-14T00:00:00Zhttps://lea.verou.me/?p=1293<p><a href="https://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/images/Screen-shot-2011-09-14-at-10.33.11-.png"><img src="https://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/images/Screen-shot-2011-09-14-at-10.33.11--300x204.png" alt="" title="Screenshot of cubic-bezier.com" /></a>A few days ago, I had a talk at <a href="http://frontendconf.ch/">a conference in Zurich</a> (I’m going to write more about it in another post). The talk was about “10 things you might not know about CSS3”. The first of those things was how you can do bouncing transitions with cubic-bezier() instead of an easing keyword. As usual, my slides included a few live demos of the functionality, in which I edited the cubic-bezier() parameters and the audience could see the transition produced.</p>
<p>However, in the case of cubic-bezier() that’s not enough. No matter how much you see someone changing the parameters, if you don’t picture it in a 2D plane, it’s very hard to understand how it works. So, the night before, I searched for a tool I could use to show them how bezier curves are formed. I found plenty, but all of them restricted the the coordinates to the 0-1 range. I’m not sure if the cause is ignorance about the spec changes or that Webkit hasn’t caught up with those changes yet (<a href="https://bugs.webkit.org/show_bug.cgi?id=45761">but it will, soon</a>). The only one that supported values out of range was <a href="http://scope.bitbucket.org/ui-elements/bezier-control/index.xml">this one</a> from the Opera Dragonfly developers, but I found it kinda impossible to adapt.</p>
<p>For my talk, I tried to adapt one of them but it was late so I gave up after a while and ended up just showing them a screenshot. And the day after the talk, I started adapting <a href="http://www.roblaplaca.com/examples/bezierBuilder/">this</a> to my needs (ever tried coding at a conference? It’s awesome, you get to ask questions from very knowledgeable people and ger replies straight away). And then I started cleaning up the code, changing how it worked, adding features. At this point, I think the only thing that’s left from that tool is …the HTML5 doctype. After 3-4 days, I finished it, and got it its own domain, <a href="http://cubic-bezier.com/">cubic-bezier.com</a> (I was surprised it was still free).</p>
<h2 id="so%2C-in-a-nutshell%2C-what-makes-this-better%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/#so%2C-in-a-nutshell%2C-what-makes-this-better%3F">So, in a nutshell, what makes this better?</a></h2>
<p>Lots of things:</p>
<ul>
<li>It supports y values out of range, as per <a href="http://dev.w3.org/csswg/css3-transitions/#transition-timing-function">the latest version of the spec</a> (and shows a warning for Webkit)</li>
<li>It’s fully accessible from the keyboard</li>
<li>You can move the handles not only by dragging but also by clicking on the plane or using the keyboard arrow keys</li>
<li>You can mouse over the plane and see the progression and time percentages that correspond to every point</li>
<li>You can save curves you like in your “Library” (uses localStorage to persist them)</li>
<li>You can import and export curves to/from your library to share them with others</li>
<li>You can share a permalink to every curve. For example, <a href="http://cubic-bezier.com/#.64,.57,.67,1.53">here’s a bouncing transition</a> (FF & Opera only)</li>
<li>You can compare the current curve with any in your library, setting the duration yourself</li>
<li>Custom favicon that reflects the current curve</li>
</ul>
<h2 id="cool-stuff-used" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/#cool-stuff-used">Cool stuff used</a></h2>
<p>Given that this tool is not only for developers, but for badass developers that care about stuff like cubic-bezier(), I think I can safely assume they’re using a top notch browser. So, I went crazy with using cool modern stuff:</p>
<ul>
<li>HTML5: Canvas, localStorage, History API, range inputs, oninput event, output, classList, data- attributes</li>
<li>ES5: Accessors, Array#map, Array#forEach</li>
<li>Selectors API</li>
<li>JSON</li>
<li>CSS3: Transitions, gradients, media queries, border-radius, shadows, :in-range pseudoclass, box-sizing, transforms, text-overflow</li>
</ul>
<p>I also used my tiny chaining framework, <a href="http://lea.verou.me/chainvas">Chainvas</a> throughout this project.</p>
<h2 id="browser-support" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/#browser-support">Browser support</a></h2>
<p>So far, I’ve tested it in modern versions of Chrome, Firefox, Opera and Safari and it seems to work. I haven’t tested it in IE10 (too lazy to open vm), although I want it to work there too, so if it doesn’t let me know. :)</p>
<p>Enjoy! <a href="http://cubic-bezier.com/">cubic-bezier.com</a></p>
Major update to Chainvas: modularity, a client side build script & more2011-09-18T00:00:00Zhttps://lea.verou.me/?p=1306<p><a href="https://lea.verou.me/2011/09/major-update-to-chainvas-modularity-a-client-side-build-script-more/images/Screen-shot-2011-11-15-at-14.57.17-.png"><img src="https://lea.verou.me/2011/09/major-update-to-chainvas-modularity-a-client-side-build-script-more/images/Screen-shot-2011-11-15-at-14.57.17--300x187.png" alt="" title="Chainvas project page screenshot" /></a>A week ago, <a href="http://lea.verou.me/2011/09/chainvas-make-apis-chainable-enhance-the-canvas-api/">I released Chainvas</a>. It was a spin-off script I wrote while developing <a href="http://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/">my cubic-bezier tool</a>, to make using the Canvas API a bit less painful. However, unlike similar attempts to make the Canvas API chainable, most of my code was written in a very generic manner, and was actually able to make every API chainable. However, when I released it, even though I mentioned that it can be used for other APIs and provided some examples, practically everyone that shared the link on twitter or other means (thank you .net magazine for the newsletter mention btw!) focused on what Chainvas did for Canvas.</p>
<p><img src="http://lea.verou.me/chainvas/img/madewith.png" alt="" />Actually, while using Chainvas myself, I found it immensely more useful for chaining DOM methods and setting multiple element properties at once. Chainvas had a lot of potential, that most people were missing. And then it dawned on me: I should modularize the library! A generic chaining library at its core and additional modules for making the different APIs chainable. And I did it.</p>
<p>On the way to that, I added IE8 compatibility, and tested in many other browsers, thanks to <a href="http://www.browserstack.com/">Browserstack</a>. I actually found that Chainvas’ core even works in IE6! I also wrote <a href="http://lea.verou.me/chainvas/unit-tests.html">unit tests</a>, a much more extensive <a href="http://lea.verou.me/chainvas/#documentation">documentation</a>, added a script generated table of contents and designed <a href="http://lea.verou.me/chainvas/img/logo.svg">a logo</a> and a <a href="http://lea.verou.me/chainvas/img/madewith.svg">Chainvas pride banner</a>.</p>
<p>Also, since it was now modular, it needed a build script. I badly wanted to make this client side, so I followed this architecture:</p>
<ul>
<li>Every module is included in chainvas.js and chainvas.min.js, along with a header comment that follows <a href="http://lea.verou.me/chainvas/#making-your-own-modules">a specific syntax</a>.</li>
<li>The user selects a compression level and then, the relevant script is downloaded through XHR and split into parts according to the module headers. Then a module list is generated with checkboxes for the user to select the ones they want to include.</li>
<li>When the user checks and unchecks those checkboxes, the URL of the download link changes to a data URI that contains the script.</li>
</ul>
<p>This approach has the disadvantage that there is no default filename, and the “Save page as…” link is deactivated in Chrome (why Chrome??). However, I like the idea so much, I don’t mind these shortcomings.</p>
<p>That’s about it. <a href="http://lea.verou.me/chainvas/#documentation">Enjoy</a> and let me know about any bugs.</p>
My experience from Frontendconf Zurich2011-09-20T00:00:00Zhttps://lea.verou.me/?p=1323<p>I’m writing this blog post while eating some of the amazing Lindt chocolates I got for free 10 days ago at <a href="http://frontendconf.ch/">Frontend conference in Zurich</a>. But it wasn’t a good experience only because of them!</p>
<p>First of all, it gave me the opportunity to visit Zurich for free, and meet an old friend for the first time. A girl we used to be penpals with at primary school & junior high when she was still living in Athens and I in Lesvos. She is now living in Zurich and doing her PhD in ETH. I arrived in Zurich a day earlier and stayed in her place that first night. We caught up and I had a great time.</p>
<p>Secondly, the rest of the speakers are great people and fun too, it was a pleasure to meet them. Especially <a href="http://smashingmagazine.com/">Smashing Magazine</a>’s Vitaly Friedman. He’s a very kind guy, nothing like what you’d expect from somebody so successful. I also got the chance to meet <a href="http://robertnyman.com/">Robert</a> again, who was lots of fun as always. Those Swedes have a great sense of humor!</p>
<p>The conference itself was very nice, although small (only 200 people). Many inspiring talks, although I couldn’t attend them all because they were split into multiple tracks in one day. I would very much prefer it if it had 1 track and was 2 days. The 2nd day was an unconference, where attendees could speak, about whatever they wanted. I decided to get some sleep the second day, so I arrived a bit later, and didn’t attend many talks. It was kinda sad that it finished so early, around 4pm almost everyone was gone and most speakers were flying back the same day.</p>
<p>My talk went great, although I had the most technical glitches I’ve ever faced in a talk. That was my fault, not the conference’s. I guess I should learn to stop tweaking my slides at the last moment, cause things might break (and this time they did). Despite those glitches however, the audience loved it. Here’s a small sample of the twitter feedback I got:</p>
<p><a href="https://twitter.com/frontendconfch/status/112089631616532480">https://twitter.com/frontendconfch/status/112089631616532480</a></p>
<p><a href="https://twitter.com/FabianBeiner/status/112089949096001536">https://twitter.com/FabianBeiner/status/112089949096001536</a></p>
<p><a href="https://twitter.com/michalbe/status/112090281519751168">https://twitter.com/michalbe/status/112090281519751168</a></p>
<p><a href="https://twitter.com/jfahrenkrug/status/112091377571074048">https://twitter.com/jfahrenkrug/status/112091377571074048</a></p>
<p><a href="https://twitter.com/kcornelius/status/112091409833668608">https://twitter.com/kcornelius/status/112091409833668608</a></p>
<p><a href="https://twitter.com/backflip/status/112091599823056896">https://twitter.com/backflip/status/112091599823056896</a></p>
<p><a href="https://twitter.com/cainvommars/status/112091892581285888">https://twitter.com/cainvommars/status/112091892581285888</a></p>
<p><a href="https://twitter.com/euklid/status/112093174897459200">https://twitter.com/euklid/status/112093174897459200</a></p>
<p><a href="https://twitter.com/shvi/status/112095896040243200">https://twitter.com/shvi/status/112095896040243200</a></p>
<p><a href="https://twitter.com/lorentzforce/status/112096221350461441">https://twitter.com/lorentzforce/status/112096221350461441</a></p>
<p><a href="https://twitter.com/mettlerd/status/112101440541032448">https://twitter.com/mettlerd/status/112101440541032448</a></p>
<p><a href="https://twitter.com/FabianBeiner/status/112101569058713600">https://twitter.com/FabianBeiner/status/112101569058713600</a></p>
<p><a href="https://twitter.com/jfahrenkrug/status/112101867424718848">https://twitter.com/jfahrenkrug/status/112101867424718848</a></p>
<p><a href="https://twitter.com/jfahrenkrug/status/112103061736009729">https://twitter.com/jfahrenkrug/status/112103061736009729</a></p>
<p><a href="https://twitter.com/Schnitzel/status/112103388187070464">https://twitter.com/Schnitzel/status/112103388187070464</a></p>
<p><a href="https://twitter.com/marcoegli/status/112103726835175424">https://twitter.com/marcoegli/status/112103726835175424</a></p>
<p><a href="https://twitter.com/FabianBeiner/status/112103972227133440">https://twitter.com/FabianBeiner/status/112103972227133440</a></p>
<p><a href="https://twitter.com/mauricenaef/status/112104694171705344">https://twitter.com/mauricenaef/status/112104694171705344</a></p>
<p><a href="https://twitter.com/lulezi/status/112105754789560320">https://twitter.com/lulezi/status/112105754789560320</a></p>
<p><a href="https://twitter.com/smash%5C_it%5C_on/status/112107410155515904">https://twitter.com/smash\_it\_on/status/112107410155515904</a></p>
<p><a href="https://twitter.com/walktheweb/status/112107790918615040">https://twitter.com/walktheweb/status/112107790918615040</a></p>
<p><a href="https://twitter.com/andypanix/status/112113881966579712">https://twitter.com/andypanix/status/112113881966579712</a></p>
<p><a href="https://twitter.com/lorentzforce/status/112121470355914752">https://twitter.com/lorentzforce/status/112121470355914752</a></p>
<p><a href="https://twitter.com/loleg/status/112126016213876738">https://twitter.com/loleg/status/112126016213876738</a></p>
<p><a href="https://twitter.com/whitefleaCH/status/112132969468137472">https://twitter.com/whitefleaCH/status/112132969468137472</a></p>
<p><a href="https://twitter.com/Juztin/status/112195913929326592">https://twitter.com/Juztin/status/112195913929326592</a></p>
<p><a href="https://twitter.com/codepo8/status/112252082718916608">https://twitter.com/codepo8/status/112252082718916608</a></p>
<p><a href="https://twitter.com/derSchepp/status/112275821703602176">https://twitter.com/derSchepp/status/112275821703602176</a></p>
<p><a href="https://twitter.com/susanjrobertson/status/112286939964641280">https://twitter.com/susanjrobertson/status/112286939964641280</a></p>
<p>If you read the above carefully, you might have noticed that my talk was recorded, so you can see it too. :) Enjoy!</p>
PrefixFree: Break free from CSS prefix hell!2011-10-12T00:00:00Zhttps://lea.verou.me/?p=1365<p><a href="https://lea.verou.me/2011/10/prefixfree-break-free-from-css-prefix-hell/images/Screen-shot-2011-11-15-at-14.33.38-.png"><img src="https://lea.verou.me/2011/10/prefixfree-break-free-from-css-prefix-hell/images/Screen-shot-2011-11-15-at-14.33.38--300x187.png" alt="" title="-prefix-free project page screenshot" /></a>I wrote this script while at the airport travelling to Oslo and during the <a href="http://www.frontend2011.com/">Frontend 2011 conference</a>. I think it’s amazing, and it makes authoring CSS3 a pleasure.</p>
<p><a href="http://coding.smashingmagazine.com/2011/10/12/prefixfree-break-free-from-css-prefix-hell/">Read my announcement about it on Smashing Magazine.</a></p>
<p>Hope you like it!</p>
Easily keep gh-pages in sync with master2011-10-13T00:00:00Zhttps://lea.verou.me/?p=1368<p>I always loved Github’s ability to publish pages for a project and get the strain out of your server. However, every time I tried it, I struggled to keep the gh-pages branch up to date. Until I discovered the awesome <code>git rebase</code>.</p>
<p>Usually my github workflow is like this:</p>
<p>git add .
git status // to see what changes are going to be commited
git commit -m ‘Some descriptive commit message’
git push origin master</p>
<p>Now, when I use gh-pages, there are only a few more commands that I have to use after the above:</p>
<p>git checkout gh-pages // go to the gh-pages branch
git rebase master // bring gh-pages up to date with master
git push origin gh-pages // commit the changes
git checkout master // return to the master branch</p>
<p>I know this is old news to some of you (I’m a github n00b, struggling with basic stuff, so my advice is probably for other n00bs), but if I had read this a few months ago, it would’ve saved me big hassles, so I’m writing it for the others out there that are like me a few months ago.</p>
<p>Now if only I find an easy way to automate this… :)</p>
Optimizing long lists of yes/no values with JavaScript2011-10-19T00:00:00Zhttps://lea.verou.me/?p=1373<p><a href="http://coding.smashingmagazine.com/2011/10/19/optimizing-long-lists-of-yesno-values-with-javascript/">My newest article on Smashing Magazine’s coding section</a> is for the geekiest among you. It’s about how you can pack long lists of boolean values into a string in the most space-efficient way. Hope you enjoy it :)</p>
My experience from Fronteers, JSConf EU, Frontend and FromTheFront2011-10-21T00:00:00Zhttps://lea.verou.me/?p=1377<p>This month has been very busy conference-wise. I had 4 conferences in a row, so I was flying from country to country and giving talks for 2 weeks. As I usually do after conferences, this post sums up my experiences and feedback I got from these conferences, in chronological order.</p>
<h2 id="fromthefront" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/10/my-experience-from-fronteers-jsconf-eu-frontend-and-fromthefront/#fromthefront"><a href="http://conf2011.fromthefront.it/">FromTheFront</a></a></h2>
<p>This was a rather low-budget Italian conference that took place in Cesena, a city near Bologna. Despite the extremely low ticket price, they managed to pull off a very decent one day conference, which is very admirable. Italian food is so good that I’d recommend visiting this country even if it’s just for the food! They were very nice hosts, and I thoroughly enjoyed my time there.</p>
<p>My talk was right after <a href="http://adactio.com/">Jeremy Keith</a>’s, who is a very well-known and experienced speaker that knows how to make audiences delirious (in a good way), so I was naturally a bit nervous about the unavoidable comparison. Despite my fears, my talk was very well received. Here’s a sample of the twitter feedback I got:</p>
<p><a href="https://twitter.com/cedmax/status/119499350039740416">https://twitter.com/cedmax/status/119499350039740416</a></p>
<p><a href="https://twitter.com/caludio/status/119500241358696448">https://twitter.com/caludio/status/119500241358696448</a></p>
<p><a href="https://twitter.com/andypanix/status/119502579943223296">https://twitter.com/andypanix/status/119502579943223296</a></p>
<p><a href="https://twitter.com/verlok/status/119511814827556864">https://twitter.com/verlok/status/119511814827556864</a></p>
<p><a href="https://twitter.com/fburatti/status/119512158500433921">https://twitter.com/fburatti/status/119512158500433921</a></p>
<p><a href="https://twitter.com/Facens/status/119513991616794624">https://twitter.com/Facens/status/119513991616794624</a></p>
<p><a href="https://twitter.com/matteocollina/status/119506533548699648">https://twitter.com/matteocollina/status/119506533548699648</a></p>
<h2 id="jsconf-eu" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/10/my-experience-from-fronteers-jsconf-eu-frontend-and-fromthefront/#jsconf-eu"><a href="http://jsconf.eu/2011/">JSConf EU</a></a></h2>
<p>Next stop was Berlin and JSConf’s European sister conference. This was one of the most well organized conferences I’ve been to: The food, the coffee, the afterparties, the wifi, the projectors, everything was top notch. Also, it had a get-together the day after the conference (called “hangover.js”) which I think is great and more conferences should start adopting this tradition. It eases the pain of the conference being over and you get to say goodbye to a few folks you weren’t able to catch at the afterparty. It also featured many cool ideas, like a gal drawing live visualizations of the talks (<a href="http://www.flickr.com/photos/frauleinschiller/6237396700/in/set-72157627752113223/">Here’s mine</a>) and <a href="http://vimeo.com/29873668">a singer to open the conference in the first day singing a song to …Brendan Eich</a> (!). I made new friends, had lots of fun and everything was awesome.</p>
<p>I was a bit more nervous about my talk for two reasons: Firstly, it was my first JavaScript talk, and secondly, it had no live demos like my CSS talks, which is a big part of why people like them. It went much better than I expected, and I got very good feedback and even though I went hugely overtime (I had 30 minutes and did 55!) nobody complained. Thankfully, it was right before lunch so I didn’t eat up another speaker’s time (which is part of the reason I love the pre-lunch spot so much). I didn’t get the super-enthusiastic feedback I get from my CSS talks, but it was good enough to not be disappointed. Here’s a sample:</p>
<p><a href="https://twitter.com/codepo8/status/120450328318590976">https://twitter.com/codepo8/status/120450328318590976</a></p>
<p><a href="https://twitter.com/WebReflection/status/120457934152007680">https://twitter.com/WebReflection/status/120457934152007680</a></p>
<p><a href="https://twitter.com/nddrylliog/status/120457956830621696">https://twitter.com/nddrylliog/status/120457956830621696</a></p>
<p><a href="https://twitter.com/cramforce/status/120459076604928000">https://twitter.com/cramforce/status/120459076604928000</a></p>
<p><a href="https://twitter.com/wpbasti/status/120462654316883968">https://twitter.com/wpbasti/status/120462654316883968</a></p>
<p><a href="https://twitter.com/claudiopro/status/120465480380203008">https://twitter.com/claudiopro/status/120465480380203008</a></p>
<p><a href="https://twitter.com/VeganBen/status/120466919001300992">https://twitter.com/VeganBen/status/120466919001300992</a></p>
<p><a href="https://twitter.com/nddrylliog/status/120469327110619137">https://twitter.com/nddrylliog/status/120469327110619137</a></p>
<p>You can find my slides on <a href="http://speakerdeck.com/u/leaverou/p/polyfilling-the-gaps">Speakerdeck</a> , <a href="http://www.slideshare.net/LeaVerou/polyfilling-the-gaps">Slideshare</a> or <a href="http://talks.verou.me/polyfilling-the-gaps">the HTML version on my website</a>.</p>
<h2 id="fronteers" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/10/my-experience-from-fronteers-jsconf-eu-frontend-and-fromthefront/#fronteers"><a href="http://fronteers.nl/congres/2011">Fronteers</a></a></h2>
<p>I was looking forward to Fronteers the most, since it’s my favorite conference. It might not be the one with the most money or the biggest, but it has a special place in my heart for a number of different reasons (not all of which I can write in a public blog post). It was the first international conference I ever attended (in 2010) and I’ve met there so many people I used to only know (and admire) as a name & avatar before. It’s the conference I’ve had the most fun at, in both years I’ve been there. Everyone, the volunteers, the attendees, the speakers, everyone is awesome. There is something magic about this conference, as most of its speakers and attendees think about it in the same way (Christian Heilmann for example calls it “his special conference” and he goes to A LOT of conferences). It doesn’t just feel like a professional conference, it feels like a big, loving, open, web development family that gets together once a year to celebrate the advances in our field.</p>
<p>But this time, I wasn’t just an attendee. I wasn’t a regular speaker either. I was also hosting a workshop, my first full day workshop. I was super stressed about that, and in retrospect, it was the most exhausting thing I have ever done. Some other speakers told me it felt so exhausting because it was my first, I really hope they’re right. Luckily, attendees loved it, and they didn’t seem to notice my progressively getting tired after the 4th hour. Here’s some of the feedback I got:</p>
<p><a href="https://twitter.com/flyingpinguin/status/121507980964409346">https://twitter.com/flyingpinguin/status/121507980964409346</a></p>
<p><a href="https://twitter.com/V%5C_v%5C_V/status/121511282758258688">https://twitter.com/V\_v\_V/status/121511282758258688</a></p>
<p><a href="https://twitter.com/Jessman5/status/121519177977708547">https://twitter.com/Jessman5/status/121519177977708547</a></p>
<p><a href="https://twitter.com/tforza/status/121524521709744128">https://twitter.com/tforza/status/121524521709744128</a></p>
<p><a href="https://twitter.com/ronderksen/status/121531111665971200">https://twitter.com/ronderksen/status/121531111665971200</a></p>
<p>My talk was the next day, and even though I was afraid it would be bad due to being tired from the workshop and the pre-party, I think it was my best talk ever. I was much more relaxed, and I got the most enthusiastic feedback I ever have. My hand literally got tired favoriting tweets, and I’m pretty sure I missed some. Here’s a small sample:</p>
<p><a href="https://twitter.com/smashingmag/status/121881600366608384">https://twitter.com/smashingmag/status/121881600366608384</a></p>
<p><a href="https://twitter.com/ldebrouwer/status/121881926587002881">https://twitter.com/ldebrouwer/status/121881926587002881</a></p>
<p><a href="https://twitter.com/wnas/status/121882159756750848">https://twitter.com/wnas/status/121882159756750848</a></p>
<p><a href="https://twitter.com/FronteersConf/status/121883462251720704">https://twitter.com/FronteersConf/status/121883462251720704</a></p>
<p><a href="https://twitter.com/ldebrouwer/status/121884227083042816">https://twitter.com/ldebrouwer/status/121884227083042816</a></p>
<p><a href="https://twitter.com/addy%5C_osmani/status/121885202581684224">https://twitter.com/addy\_osmani/status/121885202581684224</a></p>
<p><a href="https://twitter.com/LuukWilms/status/121887866258333697">https://twitter.com/LuukWilms/status/121887866258333697</a></p>
<p><a href="https://twitter.com/Georg%5C_Tavonius/status/121888702678044672">https://twitter.com/Georg\_Tavonius/status/121888702678044672</a></p>
<p><a href="https://twitter.com/okonetchnikov/status/121889094346350592">https://twitter.com/okonetchnikov/status/121889094346350592</a></p>
<p><a href="https://twitter.com/decthomas/status/121889393752543233">https://twitter.com/decthomas/status/121889393752543233</a></p>
<p><a href="https://twitter.com/eising/status/121889474782314496">https://twitter.com/eising/status/121889474782314496</a></p>
<p><a href="https://twitter.com/artjulian/status/121889486358589440">https://twitter.com/artjulian/status/121889486358589440</a></p>
<p><a href="https://twitter.com/ronderksen/status/121889810230157312">https://twitter.com/ronderksen/status/121889810230157312</a></p>
<p><a href="https://twitter.com/dantz/status/121890121669804032">https://twitter.com/dantz/status/121890121669804032</a></p>
<p><a href="https://twitter.com/nerdismus/status/121890225273323520">https://twitter.com/nerdismus/status/121890225273323520</a></p>
<p><a href="https://twitter.com/okke29/status/121890604610355200">https://twitter.com/okke29/status/121890604610355200</a></p>
<p><a href="https://twitter.com/mylittletony/status/121891613139144704">https://twitter.com/mylittletony/status/121891613139144704</a></p>
<p><a href="https://twitter.com/LuukWilms/status/121893686874345472">https://twitter.com/LuukWilms/status/121893686874345472</a></p>
<p><a href="https://twitter.com/soswow/status/121893745921769472">https://twitter.com/soswow/status/121893745921769472</a></p>
<p><a href="https://twitter.com/mennovanslooten/status/121893921394655232">https://twitter.com/mennovanslooten/status/121893921394655232</a></p>
<p><a href="https://twitter.com/FronteersConf/status/121894605963804673">https://twitter.com/FronteersConf/status/121894605963804673</a></p>
<p><a href="https://twitter.com/edgarleijs/status/121894745596362752">https://twitter.com/edgarleijs/status/121894745596362752</a></p>
<p><a href="https://twitter.com/usethetics/status/121894796573937664">https://twitter.com/usethetics/status/121894796573937664</a></p>
<p><a href="https://twitter.com/peterpeerdeman/status/121895792301707264">https://twitter.com/peterpeerdeman/status/121895792301707264</a></p>
<p><a href="https://twitter.com/bjornjohansen/status/121896246809083904">https://twitter.com/bjornjohansen/status/121896246809083904</a></p>
<p><a href="https://twitter.com/cruster/status/121896792873897984">https://twitter.com/cruster/status/121896792873897984</a></p>
<p><a href="https://twitter.com/decthomas/status/121911585227816960">https://twitter.com/decthomas/status/121911585227816960</a></p>
<p><a href="https://twitter.com/aral/status/121920658778234880">https://twitter.com/aral/status/121920658778234880</a></p>
<p><a href="https://twitter.com/ldebrouwer/status/121992421474177025">https://twitter.com/ldebrouwer/status/121992421474177025</a></p>
<p><a href="https://twitter.com/wiekatz/status/121992495436537856">https://twitter.com/wiekatz/status/121992495436537856</a></p>
<p><a href="https://twitter.com/GovertVerschuur/status/121993644482891776">https://twitter.com/GovertVerschuur/status/121993644482891776</a></p>
<p><a href="https://twitter.com/johannesaxner/status/121999077859790849">https://twitter.com/johannesaxner/status/121999077859790849</a></p>
<p><a href="https://twitter.com/flurin/status/122042528794017792">https://twitter.com/flurin/status/122042528794017792</a></p>
<p>My slides are now online at <a href="http://speakerdeck.com/u/leaverou/p/css3-secrets-10-things-you-might-not-know-about-css3">Speakerdeck</a>, <a href="http://www.slideshare.net/LeaVerou/css3-secrets-10-things-you-might-not-know-about-css3">Slideshare</a> and <a href="http://talks.verou.me/css3-secrets">the interactive version on my website</a>.</p>
<h2 id="frontend-2011" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/10/my-experience-from-fronteers-jsconf-eu-frontend-and-fromthefront/#frontend-2011"><a href="http://www.frontend2011.com/">Frontend 2011</a></a></h2>
<p>Oslo is a city I’ve been to many times in the past, so there was nothing new to see there. I didn’t make it to the speakers dinner & pre-party due to my late flight, which kinda sucked but it’s my fault since it took me a long while to decide on my flight dates. The conference itself was a bit more design-focused that I’d like, but very well organized. It took place in the same hotel the speakers were staying at, which is always a good thing. It also had the best coffee I’ve ever drank at a conference, and one of the best I’ve tasted in general. I also loved the idea of having multiple projectors, so that everyone in the audience can see clearly. They had the very original idea of not only drawing caricatures for every speaker (<a href="http://www.frontend2011.com/img/speakers/lea-verou.png">here’s mine</a>, I also got it in a nice frame) but also having the artist in the venue to draw caricatures for attendees as well!</p>
<p>My talk went smoothly, and received very good feedback:</p>
<p><a href="https://twitter.com/iceMagic/status/123737225560199168">https://twitter.com/iceMagic/status/123737225560199168</a></p>
<p><a href="https://twitter.com/gustaff%5C_weldon/status/123741227563753472">https://twitter.com/gustaff\_weldon/status/123741227563753472</a></p>
<p><a href="https://twitter.com/kaelig/status/123741479142297601">https://twitter.com/kaelig/status/123741479142297601</a></p>
<p><a href="https://twitter.com/orioltf/status/123742189896474624">https://twitter.com/orioltf/status/123742189896474624</a></p>
<p><a href="https://twitter.com/chemikpil/status/123742210842820608">https://twitter.com/chemikpil/status/123742210842820608</a></p>
<p><a href="https://twitter.com/gav%5C_taylor/status/123742248646094848">https://twitter.com/gav\_taylor/status/123742248646094848</a></p>
<p><a href="https://twitter.com/mikaeljorhult/status/123742623524585473">https://twitter.com/mikaeljorhult/status/123742623524585473</a></p>
<p><a href="https://twitter.com/ken%5C_guru/status/123744573666246656">https://twitter.com/ken\_guru/status/123744573666246656</a></p>
<p><a href="https://twitter.com/cornae/status/123803246300114945">https://twitter.com/cornae/status/123803246300114945</a></p>
<p>That’s it. I now get to rest for a while. Next stop is <a href="http://swdc.se/2011/">SWDC</a> in November, which will host the première of my new talk “CSS in the 4th dimension: Not your daddy’s CSS animations” which will be about CSS transitions & animations, from the basics all way to badass secrets.</p>
<p>Thanks to all the conference organizers for inviting me and for the attendees for attending and giving feedback on my talks. You are all awesome, and it was the best 2 weeks ever. :)</p>
Animatable: A CSS transitions gallery2011-10-30T00:00:00Zhttps://lea.verou.me/?p=1420<p><a href="https://lea.verou.me/2011/10/animatable-a-css-transitions-gallery/images/Screen-shot-2011-10-30-at-08.54.38-.png"><img src="https://lea.verou.me/2011/10/animatable-a-css-transitions-gallery/images/Screen-shot-2011-10-30-at-08.54.38--300x187.png" alt="" title="Screen shot 2011-10-30 at 08.54.38" /></a>What kind of transitions can you create with only one property? This is what my new experiment, <a href="http://leaverou.github.com/animatable/">animatable</a> aims to explore.</p>
<p>It’s essentially a gallery of basic transitions. It aims to show how different animatable properties look when they transition and to broaden our horizons about which properties can be animated. Hover over the demos to see the animation in action, or click “Animate All” to see all of them (warning: might induce nausea, headache and seizures :P ). You can also click on it to see more details and get a <a href="http://leaverou.github.com/animatable/#background-size">permalink</a>. Instead of clicking, you can also navigate with the arrow keys and press Esc to return to the main listing.</p>
<p><a href="https://github.com/LeaVerou/animatable">Fork it on Github</a> and add your own ideas. Be sure to add your twitter username to them as a <code>data-author</code> attribute!</p>
<p>I’ve only tested in Firefox and Chrome for OSX so far. Not sure which other browsers are supported. However, since it uses CSS animations, we know for sure that it won’t work in browsers that don’t support CSS animations.</p>
<p>Hope you enjoy it :)</p>
Vendor prefixes have failed, what’s next?2011-11-18T00:00:00Zhttps://lea.verou.me/?p=1458<p><em><strong>Edit:</strong> This was originally written to be posted in <a href="http://lists.w3.org/Archives/Public/www-style/">www-style</a>, the mailing list for CSS development. I thought it might be a good idea to post it here as other people might be interested too. It wasn’t. Most people commenting didn’t really get the point of the article and thought I’m suggesting we should simply drop prefixes. Others think that it’s an acceptable solution for the CSS WG if CSS depends on external libraries like my own <a href="http://leaverou.github.com/prefixfree">-prefix-free</a> or LESS and SASS. I guess it was an failure of my behalf (“Know your audience”) and thus I’m disabling comments.</em></p>
<p>Discussion about prefixes was recently stirred up again by <a href="http://hsivonen.iki.fi/vendor-prefixes/">an article by Henri Sivonen</a>, so <a href="http://lists.w3.org/Archives/Public/www-style/2011Nov/0271.html">the CSS WG started debating for the 100th time</a> about when features should become unprefixed.</p>
<p>I think we need to think out of the box and come up with new strategies to solve the issues that vendor prefixes were going to fix. <strong>Vendor prefixes have failed and we can’t solve their issues by just unprefixing properties more early.</strong></p>
<h2 id="issues" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#issues">Issues</a></h2>
<p>The above might seem a bold statement, so let me try to support it by recapping the serious issues we run into with vendor prefixes:</p>
<h3 id="1.-unnecessary-bloat" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#1.-unnecessary-bloat">1. Unnecessary bloat</a></h3>
<p>Authors need to use prefixes even when the implementations are already interoperable. As a result, they end up pointlessly duplicating the declarations, making maintenance hard and/or introducing overhead from CSS pre- and post-processors to take care of this duplication. We need to find a way to reduce this bloat to <strong>only the cases where different declarations are actually needed</strong>.</p>
<h3 id="2.-spec-changes-still-break-existing-content" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#2.-spec-changes-still-break-existing-content">2. Spec changes still break existing content</a></h3>
<p>The biggest advantage of the current situation was supposed to be that spec changes would not break existing content, but prefixes have failed to even do this. The thing is, <strong>most authors will use something if it’s available</strong>, no questions asked. I doubt anyone that has done any real web development would disagree with that. And in most cases, they will prefer a slightly different application of a feature than none at all, so they use prefixed properties along with unprefixed. Then, when the WG makes a backwards-incompatible change, existing content breaks.</p>
<p>I don’t think this can really be addressed in any way except disabling the feature by default in public builds. Any kind of prefix or notation is pointless to stop this, we’ll always run into the same issue. If we disable the feature by default, almost nobody will use it since they can’t tell visitors to change their browser settings. Do we really want that? Yes, the WG will be able to make all the changes they want, but then <strong>then who will give feedback for these changes?</strong> Certainly not authors, as they will effectively have zero experience working with the feature as most of them don’t have the time to play around with features they can’t use right now.</p>
<p>I think we should accept that changes will break <em>*some*</em> existing content, and try to standardize faster, instead of having tons of features in WD limbo. However, I still think that there should be some kind of notation to denote that a feature is experimental so that at least authors know what they’re getting themselves into by using it and for browsers to be able to experiment a bit more openly. I don’t think that vendor prefixes are the right notation for this though.</p>
<h3 id="3.-web-development-has-become-a-popularity-contest" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#3.-web-development-has-become-a-popularity-contest">3. Web development has become a popularity contest</a></h3>
<p>I’ll explain this with an example: CSS animations were first supported by WebKit. People only used the <code>-webkit-</code> prefix with them and they were fine with it. Then Firefox also implemented them, and most authors started adding <code>-moz-</code> to their use cases. Usually only to the new ones, their old ones are still WebKit only. After a while, Microsoft announced CSS animations in IE10. Some authors started adding <code>-ms-</code> prefixes to their new websites, some others didn’t because IE10 isn’t out yet. When IE10 is out, they still won’t add it because their current use cases will be for the most part not maintained any more. Some authors don’t even add <code>-ms-</code> because they dislike IE. Opera will soon implement CSS animations. Who will really go back and add <code>-o-</code> versions? Most people will not care, because they think Opera has too little market share to warrant the extra bloat.</p>
<p>So browsers appear to support less features, only because authors have to take an extra step to explicitly support them. <strong>Browsers do not display pages with their full capabilities because authors were lazy, ignorant, or forgetful.</strong> This is unfair to both browser vendors and web users. We need to find a way to (optionally?) decouple implementation and browser vendor in the experimental feature notation.</p>
<h2 id="ideas" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#ideas">Ideas</a></h2>
<p>There is a real problem that vendor prefixes attempted to solve, but vendor prefixes didn’t prove out to be a good solution. I think we should start thinking outside the box and propose new ideas instead of sticking to vendor prefixes and debating their duration. I’ll list here a few of my ideas and I’m hoping others will follow suit.</p>
<h3 id="1.-generic-prefix-(-x--or-something-else)-and%2For-new-%40rule" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#1.-generic-prefix-(-x--or-something-else)-and%2For-new-%40rule">1. Generic prefix (-x- or something else) and/or new @rule</a></h3>
<p>A generic prefix <a href="http://www.quirksmode.org/blog/archives/2010/03/css_vendor_pref_1.html">has been proposed before</a>, and usually the argument against it is that different vendors may have incompatible implementations. This could be addressed at a more general level, instead of having the prefix on every feature: An @-rule for addressing specific vendors. for example:</p>
<p>@vendor (moz,webkit,o) {
.foo { -x-property: value; }
}</p>
<p>@vendor (ms) {
.foo { -x-property: other-value; }
}</p>
<p>A potential downside is selector duplication, but remember: <strong>The @vendor rule would ONLY be used when implementations are actually incompatible</strong>.</p>
<p>Of course, there’s the potential for misuse, as authors could end up writing separate CSS for separate browsers using this new rule. However, I think we’re in a stage where most authors have realized that this is a bad idea, and if they want to do it, they can do it now anyway (for example, by using @-moz-document to target Moz and so on)</p>
<h3 id="2.-supporting-both-prefixed-and-unprefixed-for-wd-features" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#2.-supporting-both-prefixed-and-unprefixed-for-wd-features">2. Supporting both prefixed and unprefixed for WD features</a></h3>
<p>This delegates the decision to the author, instead of the WG and implementors. The author could choose to play it safe and use vendor prefixes or risk it in order to reduce bloat on a per-feature basis.</p>
<p>I guess a problem with this approach is that extra properties mean extra memory, but it’s something that many browsers already do when they start supporting a property unprefixed and don’t drop the prefixed version like they should.</p>
<p><strong>Note:</strong> While this post was still in draft, I was informed that Alex Mogilevsky has suggested something very similar. <a href="http://lists.w3.org/Archives/Public/www-style/2011Nov/0346.html">Read his proposal</a>.</p>
<h3 id="3.-prefixes-for-versioning%2C-not-vendors" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#3.-prefixes-for-versioning%2C-not-vendors">3. Prefixes for versioning, not vendors</a></h3>
<p>When a browser implements a property for the first time, they will use the prefix <code>-a-</code>. Then, when another browser implements that feature, they look at the former browser’s implementation, and if theirs is compatible, they use the same prefix. If it’s incompatible, they increment it by one, using <code>-b-</code> and so on.</p>
<p>A potential problem with this is collisions: Vendors using the same prefix not because their implementations are compatible but because they developed them almost simultaneously and didn’t know about each other’s implementation. Also, it causes trouble for the smaller vendors that might want to implement a feature first.</p>
<h3 id="we-need-more-ideas" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/#we-need-more-ideas">We need more ideas</a></h3>
<p>Even if the above are not good ideas, I’m hoping that they’ll inspire others to come up with something better. I think we need more ideas about this, rather than more debates about fine-tuning the details of one bad solution.</p>
Introducing dabblet: An interactive CSS playground2011-12-15T00:00:00Zhttps://lea.verou.me/?p=1471<p><a href="https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/images/Screen-shot-2011-12-14-at-23.32.02-.png"><img src="https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/images/Screen-shot-2011-12-14-at-23.32.02--300x183.png" alt="" title="Dabblet screenshot" /></a>I loved <a href="http://jsfiddle.net/">JSFiddle</a> ever since I first used it. Being able to test something almost instantly and without littering my hard drive opened new possibilities for me. I use it daily for experiments, browser bug testcases, code snippet storage, code sharing and many other things. However, there were always a few things that bugged me:</p>
<ul>
<li>JSFiddle is very JS oriented, as you can tell even from the name itself</li>
<li>JSFiddle is heavily server-side so there’s always at least the lag of an HTTP request every time you make an action. It makes sense not to run JS on every keystroke (JSBin does it and it’s super annoying, even caused me to fall in an infinite loop once) but CSS and HTML could be updated without any such problems.</li>
<li>I’m a huge tabs fan, I hate spaces for indenting with a passion.</li>
<li>Every time I want to test a considerable amount of CSS3, I need to include <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a> as a resource and I can’t save that preference or any other (like “No library”).</li>
</ul>
<p>Don’t get me wrong, I LOVE JSFiddle. It was a pioneer and it paved the way for all similar apps. It’s great for JavaScript experiments. But for pure CSS/HTML experiments, we can do better.</p>
<p>The thought of making some interactive playground for CSS experiments was lingering in my mind for quite a while, but never attempted to start it as I knew it would be a lot of fascinating work and I wouldn’t be able to focus on anything else throughout. While I was writing <a href="http://24ways.org/2011/css3-patterns-explained">my 24ways article</a>, I wanted to include lots of CSS demos and I wanted the code to be editable and in some cases on top of the result to save space. JSFiddle’s embedding didn’t do that, so I decided to make something simple, just for that article. It quickly evolved to something much bigger, and yes I was right, it was lots of fascinating work and I wasn’t able to focus on anything else throughout. I even delayed my 24ways article for the whole time I was developing it, and I’m grateful that Drew was so patient. After 3 weeks of working on it, I present <a href="http://dabblet.com/">dabblet</a>.</p>
<h3 id="features" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/#features">Features</a></h3>
<p>So what does dabblet have that similar apps don’t? Here’s a list:</p>
<ul>
<li>Realtime updates, no need to press a button or anything</li>
<li>Saves everything to <a href="https://gist.github.com/">Github gists</a>, so even if dabblet goes away (not that I plan to!) you won’t lose your data</li>
<li>No page reloads even on saving, everything is XHR-ed</li>
<li>Many familiar keyboard shortcuts</li>
<li>Small inline previewers for many kinds of CSS values, in particular for: <a href="http://dribbble.com/shots/338667-Mystery-upcoming-project-UI-detail-CSS-color-preview">colors</a>, <a href="http://dribbble.com/shots/339917-Mystery-upcoming-project-UI-detail-Length-preview">absolute lengths</a>, durations, <a href="http://dribbble.com/shots/346253-Mystery-upcoming-project-UI-detail-Angle-preview">angles</a>, <a href="http://dribbble.com/shots/349045-Mystery-upcoming-project-UI-detail-Easing-previewer">easing functions</a> and <a href="http://dribbble.com/shots/346247-Mystery-upcoming-project-UI-detail-CSS-gradient-preview">gradients</a>. Check them all in <a href="http://dabblet.com/gist/1441328">this dabblet</a>.</li>
<li>Automatically adds prefixes with <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a>, to speed up testing</li>
<li>Use the Alt key and the up/down arrows to increment/decrement <code><length></code>, <code><time></code> and <code><angle></code> values.</li>
<li>Dabblet is <a href="https://github.com/LeaVerou/dabblet">open source</a> under a NPOSL 3.0 license</li>
<li>You can save anonymously even when you are logged in</li>
<li>Multiple view modes: Result behind code, Split views (horizontal or vertical), separate tabs. View modes can be saved as a personal preference or in the gists (as different demos may look better with different view modes)</li>
<li>Editable even from an embedded iframe (to embed just use the same dabblet URL, it will be automatically adjusted through media queries)</li>
</ul>
<p>Here’s a rough screencast that I made in 10 minutes to showcase some of dabblet’s features. There’s no sound and is super sloppy but I figured even this lame excuse of a screencast is better than none.</p>
<iframe width="600" height="500" src="http://www.youtube.com/embed/ztMJQJgTMSE" frameborder="0" allowfullscreen=""></iframe>
<p>I’m hoping to make a proper screencast in the next few days.</p>
<p>However, dabblet is still very new. I wouldn’t even call it a beta yet, more like an Alpha. I’ve tried to iron out every bug I could find, but I’m sure there are many more lingering around. Also, it has some limitations, but it’s my top priority to fix them:</p>
<ul>
<li>It’s currently not possible to see or link to older versions of a dabblet. You can of course use Github to view them.</li>
<li>It currently only works in modern, CORS-enabled browsers. Essentially Chrome, Safari and Firefox. I intend to support Opera too, once Opera 12 comes out. As for IE, I’ll bother with it when a significant percentage of web developers start using it as their main browser. Currently, I don’t know anyone that does.</li>
<li>It doesn’t yet work very well on mobile but I’m working on it and it’s a top priority</li>
<li>You can’t yet add other scripts like LESS or remove -prefix-free.</li>
<li>Hasn’t been tested in Windows very much, so not sure what issues it might have there.</li>
</ul>
<p>I hope you enjoy using it as much as I enjoyed making it. Please report any bugs and suggest new features in <a href="https://github.com/LeaVerou/dabblet/issues">its bug tracker</a>.</p>
<h2 id="examples" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/#examples">Examples</a></h2>
<p>Here are some dabblets that should get you started:</p>
<ul>
<li><a href="http://dabblet.com/gist/1441328">http://dabblet.com/gist/1441328</a></li>
<li><a href="http://dabblet.com/gist/1454230">http://dabblet.com/gist/1454230</a></li>
<li><a href="http://dabblet.com/gist/1454409">http://dabblet.com/gist/1454409</a></li>
<li><a href="http://dabblet.com/gist/1457668">http://dabblet.com/gist/1457668</a></li>
<li><a href="http://dabblet.com/gist/1457677">http://dabblet.com/gist/1457677</a></li>
<li><a href="http://dabblet.com/gist/1421054">http://dabblet.com/gist/1421054</a></li>
<li><a href="http://dabblet.com/gist/1454889">http://dabblet.com/gist/1454889</a></li>
</ul>
<h2 id="credits" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/#credits">Credits</a></h2>
<p><a href="http://kizu.ru/en/">Roman Komarov</a> helped tremendously by doing QA work on dabblet. Without his efforts, it would have been super buggy and much less polished.</p>
<p>I’d also like to thank <a href="http://twitter.com/dstorey">David Storey</a> for coming up with the name “dabblet” and for his support throughout these 3 weeks.</p>
<p>Last but not least, I’d also like to thank <a href="http://oli.jp/">Oli Studholme</a> and <a href="http://richclarkdesign.com/">Rich Clark</a> for promoting dabblet in their .net magazine articles even before its release.</p>
<p><strong>Update:</strong> Dabblet has its own twitter account now: Follow <a href="http://twitter.com/dabblet">@dabblet</a></p>
On web apps and their keyboard shortcuts2011-12-17T00:00:00Zhttps://lea.verou.me/?p=1493<p>Yesterday, <a href="http://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/">I released dabblet.</a> One of its aspects that I took extra care of, is it’s keyboard navigation. I used many of the commonly established application shortcuts to navigate and perform actions in it. Some of these naturally collided with the native browser shortcuts and I got <a href="https://github.com/LeaVerou/dabblet/issues/54">a few bug reports</a> about that. Actually, overriding the browser shortcuts was by design, and I’ll explain my point of view below.</p>
<p>Native apps use these shortcuts all the time. For example, I press Cmd+1,2,3 etc in Espresso to navigate through files in my project. People press F1 for help. And so on. These shortcuts are so ingrained in our (power users) minds and so useful that we thoroughly miss them when they’re not there. Every time I press Cmd+1 in an OSX app and I don’t go to the first tab, I’m distraught. However, in web apps, these shortcuts are taken by the browser. We either have to use different shortcuts or accept overriding the browser’s defaults.</p>
<p>Using different shortcuts seems to be considered best practice, but how useful are these shortcuts anyway? They have to be individually learned for every web app, and that’s hardly about memorizing the “keyboard shortcuts” list. Our muscles learn much more slowly than our minds. To be able to use these shortcuts as mindlessly as we use the regular application shortcuts, we need to spend a long time using the web app and those shortcuts. If we ever do get used to them that much, we’ll have trouble with the other shortcuts that most apps use, as our muscles will try to use the new ones.</p>
<p>Using the de facto standard keyboard shortcuts carries no such issues. They take advantage of muscle memory from day one. If we advocate that web is the new native, it means our web apps should be entitled to everything native apps are. If native editors can use Cmd+1 to go to the first tab and F1 for help, so should a web editor. When you’re running a web app, the browser environment is merely a host, like your OS. The focus is the web app. When you’re working in a web app and you press a keyboard shortcut, chances are you’re looking to interact with that app, not with the browser Chrome.</p>
<p>For example, I’m currently writing in Wordpress’ editor. When I press Cmd+S, I expect my draft to be saved, not the browser to attempt to save the current HTML page. Would it make sense if they wanted to be polite and chose a different shortcut, like Alt+S? I would have to learn the Save shortcut all over again and I’d forever confuse the two.</p>
<p>Of course, it depends on how you define a web app. If we’re talking about a magazine website for example, you’re using the browser as a kind of reader. The app you’re using is still the browser, and overriding its keyboard shortcuts is bad. It’s a sometimes fine distinction, and many disagreements about this issue are basically disagreements about what constitutes a web app and how much of an application web apps are.</p>
<p>So, what are your thoughts? Play it safe and be polite to the host or take advantage of muscle memory?</p>
<p><strong>Edit:</strong> <a href="http://snook.ca/">Johnathan Snook</a> posted these thoughts in the comments, and I thought his suggested approach is pure genius and every web UX person should read it:</p>
<blockquote>
<p>On Yahoo! Mail, we have this same problem. It’s an application with many of the same affordances of a desktop application. As a result, we want to have the same usability of a desktop application—including with keyboard shortcuts. In some cases, like Cmd-P for printing, we’ll override the browser default because the browser will not have the correct output.</p>
<p>For something like tab selection/editing, we don’t override the defaults and instead, create alternate shortcuts for doing so.</p>
<p>One thing I suggest you could try is to behave somewhat like overflow areas in a web page. When you scroll with a scroll mouse or trackpad in the area, the browser will scroll that area until it reaches it’s scroll limit and then will switch to scrolling the entire page. It would be interesting to experiment with this same approach with other in-page mechanisms. For example, with tabs, I often use Cmd-Shift-[ and Cmd-Shift-] to change tabs (versus Cmd-1/2/3, etc). You could have it do so within the page until it hits its limit (first tab/last tab) and then after that, let the event fall back to the browser. For Cmd-1, have it select the first tab. If the user is already on the first tab, have it fall back to the browser.</p>
</blockquote>
Dabblet blog2012-01-05T00:00:00Zhttps://lea.verou.me/?p=1531<p>Not sure if you noticed, but <a href="http://dabblet.com/">Dabblet</a> now has a blog: <a href="http://blog.dabblet.com/">blog.dabblet.com</a></p>
<p>I’ll post there about Dabblet updates and not flood my regular subscribers here who may not care. So, if you are interested on Dabblet’s progress, follow that blog or <a href="http://twitter.com/dabblet">@dabblet</a> on twitter.</p>
<p>That was also an excuse to finally try <a href="http://tumblr.com/">tumblr</a>. So far, so good. I love how it gives you custom domains and full theme control for free (hosted Wordpress charges for those). Gorgeous, GORGEOUS interface too. Most of the themes have markup from the 2005-2007 era, but that was no surprise. I customized the theme I picked to make it more HTML5-ey and more on par with dabblet’s style and it was super easy (though my attempt is by no means finished). There are a few shortcomings (like no titles for picture posts), but nothing too bad.</p>
What we still can’t do client-side2012-01-09T00:00:00Zhttps://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/<p>With the rise of all these APIs and the browser race to implement them, you’d think that currently we can do pretty much everything in JavaScript and even if we currently can’t due to browser support issues, we will once the specs are implemented. Unfortunately, that’s not true. There are still things we can’t do, and there’s no specification to address them at the time of this writing and no way to do them with the APIs we already have (or if there is a way, it’s unreasonably complicated).</p>
<h3 id="we-can%E2%80%99t-do-templating-across-pages" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/#we-can%E2%80%99t-do-templating-across-pages">We can’t do templating across pages</a></h3>
<p><strong>Before rushing to tell me “no, we can”, keep reading.</strong> I mean have different files and re-use them accross different pages. For example, a header and a footer. If our project is entirely client-side, we have to repeat them manually on every page. Of course, we can always use (i)frames, but that solution is worse than the problem it solves. There should be a simple way to inject HTML from another file, like server-side includes, but client-side. without using JavaScript at all, this is a task that belongs to HTML (with JS we can always use XHR to do it but…). The browser would then be able to cache these static parts, with significant speed improvements on subsequent page loads.</p>
<p><strong>Update:</strong> The <a href="http://webcomponents.org/">Web Components</a> family of specs sort of helps with this, but still requires a lot of DIY and <a href="https://hacks.mozilla.org/2014/12/mozilla-and-web-components/">Mozilla is against HTML imports and will not implement them</a>, which is one main component of this.</p>
<h3 id="we-can%E2%80%99t-do-localization" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/#we-can%E2%80%99t-do-localization">We can’t do localization</a></h3>
<p>At least not in a sane, standard way. Client-side localization is a big PITA. There should be an API for this. That would have the added advantage that browsers could pick it up and offer a UI for it. I can’t count the number of times I’ve thought a website didn’t have an English version just because their UI was so bad I couldn’t find the switcher. Google Chrome often detects a website language and offers to translate it, if such an API existed we could offer properly translated versions of the website in a manner detectable by the browser.</p>
<p><strong>Update:</strong> We have the <a href="http://wiki.ecmascript.org/doku.php?id=globalization:specification_drafts">ECMAScript Globalization API</a>, although it looks far from ideal at the moment.</p>
<h3 id="we-can%E2%80%99t-do-screen-capture" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/#we-can%E2%80%99t-do-screen-capture">We can’t do screen capture</a></h3>
<p>And not just of the screen, but we can’t even capture an element on the page and draw it on a canvas unless we use huge libraries that basically try to emulate a browser or SVG foreignObject which has its own share of issues. We should have a Screen Capture API, or at the very least, a way to draw DOM nodes on canvas. Yes, there are privacy concerns that need to be taken care of, but this is so tremendously useful that it’s worth the time needed to go intro researching those.</p>
<h3 id="we-can%E2%80%99t-get-post-parameters-and-http-headers" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/#we-can%E2%80%99t-get-post-parameters-and-http-headers">We can’t get POST parameters and HTTP headers</a></h3>
<p>There’s absolutely NO way to get the POST parameters or the HTTP response headers that the current page was sent with. You can get the GET parameters through the location object, but no way to get POST parameters. This makes it very hard to make client-side applications that accept input from 3rd party websites when that input is too long to be on the URL (as is the case of <a href="http://dabblet.com/">dabblet</a> for example).</p>
<h3 id="we-can%E2%80%99t-make-peer-to-peer-connections" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/#we-can%E2%80%99t-make-peer-to-peer-connections">We can’t make peer to peer connections</a></h3>
<p>There is absolutely no way to connect to another client running our web app (to play a game for example), without an intermediate server.</p>
<p><strong>Update:</strong> There’s RTCPeerConnection in <a href="http://w3c.github.io/webrtc-pc/">WebRTC</a>, though the API is pretty horrible.</p>
<p>_________</p>
<p>Anything else we still can’t do and we still don’t have an API to do so in the future? Say it in the comments!</p>
<p>Or, if I’m mistaken about one of the above and there is actually an active spec to address it, please point me to it!</p>
<h3 id="why-would-you-want-to-do-these-things-client-side%3F!" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/#why-would-you-want-to-do-these-things-client-side%3F!">Why would you want to do these things client-side?!</a></h3>
<p>Everything that helps take load away from the server is good. The client is always one machine, everything on the server may end up running thousands of times per second if the web app succeeds, making the app slow and/or costly to run. I strongly believe in lean servers. Servers should only do things that architecturally need a server (e.g. centralized data storage), everything else is the client’s job. Almost everything that we use native apps for, should (and eventually will) be doable by JavaScript.</p>
My new year’s resolution2012-01-13T00:00:00Zhttps://lea.verou.me/?p=1545<p><strong>Warning: Personal post ahead. If you’re here to read some code trickery, move along and wait for the next post, kthxbai</strong></p>
<p>Blogs are excellent places for new year’s resolutions. Posts stay there for years, to remind you what you’ve been thinking long ago. A list on a piece of paper or a file in your computer will be forgotten and lost, but a resolution on your blog will come back to haunt you. Sometimes you want that extra push. I’m not too fond of new year’s resolutions and this may as well be my first, but this year there are certain goals I want to achieve, unlike previous years were things were more fluid.</p>
<p>So, in 2012 I want to…</p>
<ul>
<li>Land my dreamjob in a US company/organization I respect</li>
<li>Get the hell out of Greece and move to the Bay Area</li>
<li>Strive to improve my english even more, until I sound and write like a native speaker</li>
<li>Find a publisher I respect that’s willing to print in full color and write my first book.</li>
<li>Stop getting into stupid fights on twitter. They are destructive to both my well-being and my creativity.</li>
<li>Get my degree in Computer Science. This has been my longest side project, 4 years and counting.</li>
</ul>
<p>I wonder how many of those I will have achieved this time next year, how many I will have failed and how many I won’t care about any more…</p>
Why tabs are clearly superior2012-01-17T00:00:00Zhttps://lea.verou.me/?p=1552<p>If you follow me <a href="https://twitter.com/leaverou">on twitter</a> or have heard one of my talks you’ll probably know I despise spaces for indentation with a passion. However, I’ve never gone into the details of my opinion on stage, and twitter isn’t really the right medium for advocacy.
I always wanted to write a blog post about my take on this old debate, so here it is.</p>
<h2 id="tabs-for-indentation%2C-spaces-for-alignment" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#tabs-for-indentation%2C-spaces-for-alignment">Tabs for indentation, spaces for alignment</a></h2>
<p>Let’s get this out of the way: <strong>Tabs should <em>never</em> be used for alignment.</strong>
<strong>Using tabs for alignment is actively worse than using spaces for indentation</strong> and is the base of all arguments against tabs.
But using tabs for alignment is misuse, and negates their main advantage: personalization.
It’s like trying to eat soup with a fork and complaining because it doesn’t scoop up liquid well.</p>
<p>Consider this code snippet:</p>
<pre><code class="language-js">if (something) {
let x = 10,
y = 0;
}
</code></pre>
<p>Each line inside the conditional is indented with a tab, but the variables are aligned with four spaces.
Change the tab size to see how everything adapts beautifully:</p>
<label id="tab_size">
Tab size:
<input type="range" min="1" max="8" value="4" oninput="this.nextSibling.textContent = document.body.style.tabSize = this.value;" />
4
</label>
<p>And yes, of course using tabs for alignment is a mess, because that’s not what they’re for:</p>
<pre><code class="language-js">if (something) {
let x = 10,
y = 0;
}
</code></pre>
<p>Another example: remember CSS vendor prefixes?</p>
<pre><code class="language-css">div {
-webkit-transition: 1s;
-moz-transition: 1s;
-ms-transition: 1s;
-o-transition: 1s;
transition: 1s;
}
</code></pre>
<h2 id="1.-tabs-can-be-personalized" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#1.-tabs-can-be-personalized">1. Tabs can be personalized</a></h2>
<p>The width of a tab character can be adjusted per editor. This is not a disadvantage of tabs as commonly evangelized, but <strong>a major advantage</strong>.
<strong>People can view your code in the way they feel comfortable with, not in the way *you* prefer.</strong>
Tabs are one step towards decoupling the code’s presentation from its logic, just like CSS decouples presentation from HTML.
They give more power to the reader rather than letting the author control everything.
Basically, using spaces is like saying “I don’t care about how you feel more comfortable reading code, I will force you to use <em>my</em> preferences because it’s <em>my</em> code”.</p>
<p>Personalization is incredibly valuable when a team is collaborating, as different engineers can have different opinions.
Some engineers prefer their indents to be 2 spaces wide, some prefer them to be 4 spaces wide.
With spaces for alignment, a lead engineer imposes their preference on the entire team; with tabs everyone gets to choose what <em>they</em> feel comfortable with.</p>
<h2 id="2.-you-don%E2%80%99t-depend-on-certain-tools" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#2.-you-don%E2%80%99t-depend-on-certain-tools">2. You don’t depend on certain tools</a></h2>
<p>When using spaces, you depend on your editor to abstract away the fact that an indent is actually N characters instead of one.
You depend on your editor to insert N spaces every time you press the Tab key and to delete N characters every time you press backspace or delete near an indent.
I have never seen an editor where this abstraction did not <a href="https://en.wikipedia.org/wiki/Leaky_abstraction">leak</a> at all.
If you’re not careful, it’s easy to end up with indentation that is not an integer multiple of the indent width, which is a mess.
With tabs, the indent width is simply the number of tabs at the beginning of a line.
You don’t depend on tools to hide anything, and change the meaning of keyboard keys.
Even in the most basic of plain text editors, you can use the keyboard to navigate indents in integer increments.</p>
<h2 id="3.-tabs-encode-strictly-more-information-about-the-code" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#3.-tabs-encode-strictly-more-information-about-the-code">3. Tabs encode strictly more information about the code</a></h2>
<p>Used right, tabs are only used for a singular purpose: indentation.
This makes them easy to target programmatically, e.g. through regular expressions or find & replace.
Spaces on the other hand, have many meanings, so programmatically matching indents is a non-trivial problem.
Even if you only match space characters at the beginning of a line, there is no way of knowing when to stop, as spaces are also used for alignment.
Being able to tell the difference requires awareness about the semantics of the language itself.</p>
<h2 id="4.-tabs-facilitate-portability" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#4.-tabs-facilitate-portability">4. Tabs facilitate portability</a></h2>
<p>As <a href="http://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#comment-415098853">pointed out by Norbert Süle in the comments</a>, when you copy and paste code that’s indented with spaces, you have to manually adjust the indentation afterwards, unless the other person also <em>happens</em> to prefer the same width indents as you.
With tabs, there is no such issue, as it’s always tabs so it will fit in with your (tabbed) code seamlessly. The world would be a better place if everyone used tabs.</p>
<h2 id="5.-tabs-take-up-less-space" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#5.-tabs-take-up-less-space">5. Tabs take up less space</a></h2>
<p>One of the least important arguments here, but still worth mentioning.
Tabs take up only one byte, while spaces take up as many bytes as their width, usually 2-4x that.
On large codebases this can add up. E.g. in a codebase of 1M loc, averaging 1 indent per line (good luck computing these kinds of stats with spaces btw, see 3 above), with an indent width of 4 spaces, you would save 3MB of space by using tabs instead of spaces.
It’s not a tremendous cost if spaces actually offered a benefit, but it’s unclear what the benefit is.</p>
<h2 id="the-downsides-of-using-tabs-for-indentation" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#the-downsides-of-using-tabs-for-indentation">The downsides of using tabs for indentation</a></h2>
<p>Literally all downsides of using tabs for indentation stem from how vocal their opponents are and how pervasive spaces are for indentation.
To the point that <a href="https://arstechnica.com/information-technology/2017/06/according-to-statistics-programming-with-spaces-instead-of-tabs-makes-you-rich/">using spaces for indentation is associated with <em>significantly</em> higher salaries</a>!</p>
<h3 id="in-browsers" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#in-browsers">In browsers</a></h3>
<p>It is unfortunate that most UAs have declared war to tabs by using a default tab size of 8, far too wide for any practical purpose.
For code you post online, you can use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size"><code>tab-size</code></a> property to set tab size to a more reasonable value, e.g. 4.
It’s <a href="https://caniuse.com/css3-tabsize">widely supported</a>.</p>
<p>For reading code on other websites, you can use an extension like <a href="https://chrome.google.com/webstore/detail/stylus/clngdbkpkpeebahjckkjfobafhncgmne">Stylus</a> to set the tab size to whatever you want.
I have this rule applying on all websites:</p>
<pre><code class="language-css">/* ==UserStyle==
@name 7/22/2022, 5:43:07 PM
@namespace *
@version 1.0.0
@description A new userstyle
@author Me
==/UserStyle== */
* {
tab-size: 4 !important;
}
</code></pre>
<h3 id="in-tooling" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#in-tooling">In tooling</a></h3>
<p>Editors that handle smart tabs correctly are few and far between.
Even VS Code, the most popular editor right now, <a href="https://github.com/microsoft/vscode/issues/33974">doesn’t handle them correctly</a>,
though there are extensions (<a href="https://marketplace.visualstudio.com/items?itemName=j-zeppenfeld.tab-indent-space-align">Tab-Indent Space-Align</a>, <a href="https://marketplace.visualstudio.com/items?itemName=Valsorym.smart-tabs#:~:text=Smart%20Tabs%20for%20Visual%20Studio,a%20multitude%20of%20open%20files.">Smart Tabs</a>, and others)</p>
<h2 id="what-does-it-matter%2C-tabs%2C-spaces%2C-whatever%2C-it%E2%80%99s-just-a-pointless-detail" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#what-does-it-matter%2C-tabs%2C-spaces%2C-whatever%2C-it%E2%80%99s-just-a-pointless-detail">What does it matter, tabs, spaces, whatever, it’s just a pointless detail</a></h2>
<p>Sure, in the grand scheme of things, using spaces for indentation will not kill anyone.
But it’s a proxy for a greater argument:
that technology should make it possible to read code in the way <em>you</em> prefer,
without having to get team buy-in on your preferences.
There are other ways to do this (reformatting post-pull and pre-commit), but are too heavyweight and intrusive.
If we can’t even get people to see the value of not enforcing the same indentation width on everyone,
how can we expect them to see the value in further personalization?</p>
<h2 id="further-reading" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/#further-reading">Further reading</a></h2>
<ul>
<li><a href="https://lb-stuff.com/tabs">Tabs. Spaces. Indentation. Alignment.</a></li>
<li><a href="http://mystilleef.blogspot.com/2006/11/indentation-with-spaces-considered.html">Indentation With Spaces Considered Harmful</a></li>
<li><a href="http://www.rizzoweb.com/java/tabs-vs-spaces.html">Tabs vs spaces for code indentation</a></li>
<li><a href="http://derkarl.org/why_to_tabs.html">Why I love having tabs in source code</a></li>
<li><a href="http://blogs.msdn.com/b/cyrusn/archive/2004/09/14/229474.aspx">Tabs vs spaces</a></li>
<li><a href="https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces">Indent with tabs, align with spaces</a></li>
<li>Relevant: <a href="http://nickgravgaard.com/elastictabstops/">Elastic tabstops</a></li>
</ul>
<p><em>Thanks to <a href="https://twitter.com/boblet">Oli</a> for proofreading the first version of this post.</em></p>
Exactly how much CSS3 does your browser support?2012-02-02T00:00:00Zhttps://lea.verou.me/?p=1562<p><a href="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/images/Screen-shot-2012-02-02-at-14.20.15-.png"><img src="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/images/Screen-shot-2012-02-02-at-14.20.15--300x187.png" alt="" title="Screen shot 2012-02-02 at 14.20.15" /></a>This project started as an attempt to improve <a href="http://dabblet.com/">dabblet</a> and to generate data for the book chapter I’m writing for Smashing Book #3. I wanted to create a very simple/basic testsuite for CSS3 stuff so that you could hover on a e.g. CSS3 property and you got a nice browser support popup. While I didn’t achieve that (turns out BrowserScope doesn’t do that kind of thing), I still think it’s interesting as a spin-off project, especially since the results will probably surprise you.</p>
<h3 id="how-it-works" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/#how-it-works">How it works</a></h3>
<p>css3test (very superficially) tests pretty much everything in the specs mentioned on the sidebar (not just the popular widely implemented stuff). You can click on every feature to expand it and see the exact the testcases run and whether they passed. <strong>It only checks what syntax the browser recognizes, which doesn’t necessarily mean it will work correctly when used.</strong> WebKit is especially notorious for cheating in tests like this, recognizing stuff it doesn’t understand, like the values “round” and “space” for background-repeat, but the cheating isn’t big enough to seriously compromise the test.</p>
<p><strong>Whether a feature is supported with a prefix or not doesn’t matter for the result.</strong> If it’s supported without a prefix, it will test that one. If it’s supported only with a prefix, it will test the prefixed one. For properties especially, if an unprefixed one is supported, it will be used in all the tests.</p>
<p><strong>Only stuff that’s in a W3C specification is tested.</strong> So, please don’t ask or send pull requests for proprietary things like -webkit-gradient() or -webkit-background-clip: text; or -webkit-box-reflect and so on.</p>
<p><strong>Every feature contributes the same to the end score</strong>, as well as to the score of the individual spec, regardless of the number of tests it has.</p>
<h3 id="crazy-shit" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/#crazy-shit">Crazy shit</a></h3>
<p>Chrome may display slightly different scores (1% difference) across pageloads. It seems that for some reason, it fails the tests for border-image completely on some pageloads, which doesn’t make any sense. Whoever wants to investigate, I’d be grateful. Edit: Fixed (someone found and submitted an even crazier workaround.).</p>
<h3 id="browserscope" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/#browserscope">Browserscope</a></h3>
<p>This is the first project of mine in which I’ve used <a href="http://www.browserscope.org/user/settings">browserscope</a>. This means that your results will be sent over to its servers and aggreggated. When I have enough data, I’m gonna built a nice table for everyone to see :) In the meantime, check the <a href="http://www.browserscope.org/browse?category=usertest_agt1YS1wcm9maWxlcnINCxIEVGVzdBidzawNDA">results</a> page.</p>
<h3 id="it-doesn%E2%80%99t-work-on-my-browser%2C-u-suck!" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/#it-doesn%E2%80%99t-work-on-my-browser%2C-u-suck!">It doesn’t work on my browser, U SUCK!</a></h3>
<p>The test won’t work on dinosaur browsers like IE8, but who cares measuring their CSS3 support anyway? “For a laugh” isn’t a good enough answer to warrant the time needed.</p>
<p>If you find a bug, please remember you didn’t pay a dime for this before nagging. Politely report it on Github, or even better, fix it and send a pull request.</p>
<h3 id="why-did-you-build-it%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/#why-did-you-build-it%3F">Why did you build it?</a></h3>
<p>To motivate browsers to support the less hyped stuff, because I’m tired of seeing the same things being evangelized over and over. There’s much more to CSS3.</p>
<h3 id="current-results" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/#current-results">Current results</a></h3>
<p>At the time of this writing, these are the results for the major modern browsers:</p>
<ul>
<li>Chrome Canary, WebKit nightlies, Firefox Nightly: <strong>64%</strong></li>
<li>Chrome, IE10PP4: <strong>63%</strong></li>
<li>Firefox 10: <strong>61%</strong></li>
<li>Safari 5.1, iOS5 Safari: <strong>60%</strong></li>
<li>Opera 11.60: <strong>56%</strong></li>
<li>Firefox 9: <strong>58%</strong></li>
<li>Firefox 6-8: <strong>57%</strong></li>
<li>Firefox 5, Opera 11.1 - 11.5: <strong>55%</strong></li>
<li>Safari 5.0: <strong>54%</strong></li>
<li>Firefox 4: <strong>49%</strong></li>
<li>Safari 4: <strong>47%</strong></li>
<li>Opera 10: <strong>45%</strong></li>
<li>Firefox 3.6: <strong>44%</strong></li>
<li>IE9: <strong>39%</strong></li>
</ul>
<p>Enjoy! <a href="http://css3test.com/">css3test.com</a> <a href="https://github.com/LeaVerou/css3test">Fork css3test on Github</a> <a href="http://www.browserscope.org/browse?category=usertest_agt1YS1wcm9maWxlcnINCxIEVGVzdBidzawNDA">Browserscope results</a></p>
Simpler CSS typing animation, with the ch unit2012-02-05T00:00:00Zhttps://lea.verou.me/?p=1572<p>A while ago, <a href="http://lea.verou.me/2011/09/pure-css3-typing-animation-with-steps/">I posted</a> about how to use <code>steps()</code> as an easing function to create a typing animation that degrades gracefully.</p>
<p>Today I decided to simplify it a bit and make it more flexible, at the cost of browser support. The new version fully works in Firefox 1+ and IE10, since Opera and WebKit don’t support <a href="http://www.w3.org/TR/css3-values/#ch-unit">the ch unit</a> and even though IE9 supports it, it doesn’t support CSS animations. To put it simply, one <code>ch</code> unit is equivalent to the width of the zero (0) character of the font. So, in monospace fonts, it’s equivalent to the width of <strong>every</strong> character, since every character has the same width.</p>
<p>In the new version, we don’t need an obscuring span, so no extra HTML and it will work with non-solid backgrounds too. Also, even though the number of characters still needs to be hard-coded, it doesn’t need to be hardcoded in the animation any more, so it could be easily done through script without messing with creating/modifying stylesheets. Note how each animation only has one keyframe, and takes advantage of the fact that when the <code>from</code> (0%) and <code>to</code> (100%) keyframes are missing, the browser generates them from the fallback styles. I use this a lot when coding animations, as I hate duplication.</p>
<p>In browsers that support CSS animations, but not the ch unit (such as WebKit based browsers), the animation will still occur, since we included a fallback in ems, but it won’t be 100% perfect. I think that’s a pretty good fallback, but if it bothers you, just declare a fallback of auto (or don’t declare one at all, and it will naturally fall back to auto). In browsers that don’t support CSS animations at all (such as Opera), the caret will be a solid black line that doesn’t blink. I thought that’s better than not showing it at all, but if you disagree, it’s very easy to hide it in those browsers completely: Just swap the <code>border-color</code> between the keyframe and the <code>h1</code> rule (hint: when a <code>border-color</code> is not declared, it’s <code>currentColor</code>).</p>
<p><strong>Edit:</strong> It appears that Firefox’s support for the ch unit is a bit buggy so, the following example won’t work with the Monaco font for example. This is not the correct behavior.</p>
<p>Enjoy:</p>
<iframe style="width: 100%; height: 600px;" src="http://dabblet.com/gist/1745856" width="320" height="240"></iframe>
Moving an element along a circle2012-02-08T00:00:00Zhttps://lea.verou.me/?p=1586<p>It all started a few months ago, when <a href="http://css-tricks.com/">Chris Coyier</a> casually asked me how would I move an element along a circle, without of course rotating the element itself. If I recall correctly, his solution was to use multiple keyframes, for various points on a circle’s circumference, approximating it. I couldn’t think of anything better at the time, but the question was stuck in the back of my head. 3 months ago, I came up with a first solution. Unfortunately, it required an extra wrapper element. The idea was to use two rotate transforms with different origins and opposite angles that cancel each other at any given time. The first transform-origin would be the center of the circle path and the other one the center of the element. Because we can’t use multiple transform-origins, a wrapper element was needed.</p>
<iframe style="width: 100%; height: 500px;" src="http://jsfiddle.net/leaverou/zXPzY/embedded/result" frameborder="0" width="320" height="240"></iframe>
<p>So, even though this solution was better, I wasn’t fully satisfied with it due to the need for the extra element. So, it kept being stuck in the back of my head.</p>
<p>Recently, I <a href="http://lists.w3.org/Archives/Public/www-style/2012Feb/0201.html">suggested to www-style that transform-origin should be a list</a> and accept multiple origins and presented this example as a use case. And then <a href="http://aryeh.name/">Aryeh Gregor</a> came up with <a href="http://lists.w3.org/Archives/Public/www-style/2012Feb/0294.html">this genius idea</a> to prove that it’s already possible if you chain translate() transforms between the opposite rotates.</p>
<p>I simplified the code a bit, and here it is:</p>
<iframe style="width: 100%; height: 500px;" src="http://dabblet.com/gist/1760283" width="320" height="240"></iframe>
<p>With the tools we currently have, I don’t think it gets any simpler than that.</p>
Vendor prefixes, the CSS WG and me2012-02-09T00:00:00Zhttps://lea.verou.me/?p=1593<p>The CSS Working Group is recently discussing the very serious problem that vendor prefixes have become. We have reached a point where <strong>browsers are seriously considering to implement -webkit- prefixes</strong>, just because authors won’t bother using anything else. <strong>This is just sad.</strong> :( <a href="http://www.glazman.org/weblog/dotclear/index.php?post/2012/02/09/CALL-FOR-ACTION:-THE-OPEN-WEB-NEEDS-YOU-NOW">Daniel Glazman</a>, <a href="http://christianheilmann.com/2012/02/09/now-vendor-prefixes-have-become-a-problem-want-to-help-fix-it/">Christian Heilmann</a> and others wrote about it, making very good points and hoping that authors will wake up and start behaving. If you haven’t already, visit those links and read what they are saying. I’m not very optimistic about it, but I’ll do whatever I can to support their efforts.</p>
<p>And that brings us to the other thing that made me sad these days. 2 days ago, <a href="http://lists.w3.org/Archives/Public/www-style/2012Feb/0313.html">the CSS WG published its Minutes</a> (sorta like a meeting) and I was surprised to hear that I’ve been mentioned. My surprise quickly turned into this painful feeling in your stomach when you’re being unfairly accused:</p>
<p>tantek: Opposite is happening right now. Web standards activists are teaching
people to use -webkit-
tantek: People like Lea Verou.
tantek: Their demos are filled with -webkit-. You will see presentations
from all the web standards advocates advocating people to use
-webkit- prefixes.</p>
<p><strong>Try to picture being blamed of the very thing you hate, and you might understand how that felt.</strong> I’ve always been an advocate of inclusive CSS coding that doesn’t shut down other browsers. It’s good for future-proofing, it’s good for competition and it’s the right thing to do. Heck, <a href="http://leaverou.github.com/prefixfree/">I even made a popular script to help people adding all prefixes</a>! I’m even one of the few people in the industry who has <strong>never expressed a definite browser preference</strong>. I love and hate every browser equally, as I can see assets and defects in all of them (ok, except Safari. Safari must die :P).</p>
<p>When Tantek realized he had falsely accused me of this, <a href="http://krijnhoetmer.nl/irc-logs/css/20120207#l-1066">he corrected himself</a> in the #css IRC room on <a href="http://w3.org/">w3.org</a>:</p>
<pre><code>\[17:27\] <tantek> (ASIDE: regarding using -webkit- prefix, clarification re: Lea Verou - she's advocated using \*both\* vendor prefixed properties (multiple vendors) and the unprefixed version after them. See her talk http://www.slideshare.net/LeaVerou/css3-a-practical-introduction-ft2010-talk from Front-Trends 2010 for example. An actual example of -webkit- \*only\* prefix examples (thus implied advocacy) is Google's http://slides.html5rocks.com/ , e.g.
\[17:27\] <tantek> http://slides.html5rocks.com/#css-columns has three -webkit- property declarations starting with -webkit-column-count )
</code></pre>
<p>That’s nice of him, and it does help. At least I had a link to give to people who kept asking me on twitter if I was really the prefix monster he made me out to be. :P The problem is that not many read the IRC logs, but many more read the www-style archives. Especially since, with all this buzz, many people were directed into reading this discussion by the above articles. I don’t know how many people will be misled by Tantek’s uninformed comment without reading his correction, but I know for sure that the number is non-zero. And the worst of all is that many of them are people in the CSSWG or in the W3C in general, people who I have great respect and admiration for. And quite frankly, that sucks.</p>
<p>I don’t think Tantek had bad intentions. I’ve met him multiple times and I know he’s a nice guy. Maybe he was being lazy by making comments he didn’t check, but that’s about it. It could happen to many people. My main frustration is that it feels there is nothing I can do about it, besides answering people when they take the time to talk to me about it. I can do nothing with the ones that won’t, and that’s the majority. At least, if a forum was used over a mailing list, this could’ve been edited or something.</p>
A List Apart article: Every time you call a proprietary feature "CSS3", a kitten dies2012-02-14T00:00:00Zhttps://lea.verou.me/?p=1611<p>My first article in ALA was published today, read it here:</p>
<p><a href="http://www.alistapart.com/articles/every-time-you-call-a-proprietary-feature-css3-a-kitten-dies/">Every time you call a proprietary feature “CSS3”, a kitten dies</a></p>
<p>Some comments about it on twitter:</p>
<p><a href="https://twitter.com/kkmett/status/169424038421204994">https://twitter.com/kkmett/status/169424038421204994</a></p>
<p><a href="https://twitter.com/codepo8/status/169425611801108480">https://twitter.com/codepo8/status/169425611801108480</a></p>
<p><a href="https://twitter.com/lydiamann/status/169425704092573696">https://twitter.com/lydiamann/status/169425704092573696</a></p>
<p><a href="https://twitter.com/zeldman/status/169426392654684160">https://twitter.com/zeldman/status/169426392654684160</a></p>
<p><a href="https://twitter.com/alistapart/status/169426553787256833">https://twitter.com/alistapart/status/169426553787256833</a></p>
<p><a href="https://twitter.com/happycog/status/169426900865908737">https://twitter.com/happycog/status/169426900865908737</a></p>
<p><a href="https://twitter.com/AlanBWhitney/status/169428991470612480">https://twitter.com/AlanBWhitney/status/169428991470612480</a></p>
<p><a href="https://twitter.com/LeftyDesigner/status/169430280883544064">https://twitter.com/LeftyDesigner/status/169430280883544064</a></p>
<p><a href="https://twitter.com/dap6000/status/169432718038409216">https://twitter.com/dap6000/status/169432718038409216</a></p>
<p><a href="https://twitter.com/martuishere/status/169434598911123456">https://twitter.com/martuishere/status/169434598911123456</a></p>
<p><a href="https://twitter.com/rachelober/status/169442236709355521">https://twitter.com/rachelober/status/169442236709355521</a></p>
<p><a href="https://twitter.com/sgalineau/status/169443447554580480">https://twitter.com/sgalineau/status/169443447554580480</a></p>
<p><a href="https://twitter.com/sdague/status/169448377824722944">https://twitter.com/sdague/status/169448377824722944</a></p>
<p><a href="https://twitter.com/whitman/status/169451221495717889">https://twitter.com/whitman/status/169451221495717889</a></p>
<p><a href="https://twitter.com/peterwinnberg/status/169456073466585088">https://twitter.com/peterwinnberg/status/169456073466585088</a></p>
<p><a href="https://twitter.com/cssquirrel/status/169468390736527360">https://twitter.com/cssquirrel/status/169468390736527360</a></p>
<p><a href="https://twitter.com/jameswillweb/status/169475627274088448">https://twitter.com/jameswillweb/status/169475627274088448</a></p>
<p><a href="https://twitter.com/rogerjohansson/status/169482651198160896">https://twitter.com/rogerjohansson/status/169482651198160896</a></p>
<p><a href="https://twitter.com/beardChamp/status/169490532928720896">https://twitter.com/beardChamp/status/169490532928720896</a></p>
Flexible multiline definition lists with 2 lines of CSS 2.12012-02-24T00:00:00Zhttps://lea.verou.me/?p=1617<p>If you’ve used definition lists (<code><dl></code>) you’re aware of the problem. By default, <code><dt></code>s and <code><dd></code>s have <code>display:block</code>. In order to turn them into what we want in most cases (each <em>pair</em> of term and definition on one line) we usually employ a number of different techniques:</p>
<ul>
<li>Using a different <code><dl></code> for each pair: Style dictating markup, which is bad</li>
<li>Floats: Not flexible</li>
<li><code>display: run-in;</code> on the <code><dt></code>: Browser support is bad (No Firefox support)</li>
<li>Adding a <code><br></code> after each <code><dd></code> and setting both term and definition as <code>display:inline</code>: Invalid markup. Need I say more?</li>
</ul>
<p>If only adding <code><br></code>s was valid… Or, even better, <strong>what if we could insert <code><br></code>s from CSS? Actually, we can!</strong> As you might be aware, the CR and LF characters that comprise a line break are regular unicode characters that can be inserted anywhere just like every unicode character. They have the unicode codes 000D and 000A respectively. This means they can also be inserted as generated content, if escaped properly. Then we can use an appropriate white-space value to make the browser respect line breaks only in that part (the inserted line break). It looks like this:</p>
<p>dd:after {
content: ‘\A’;
white-space: pre;
}</p>
<p>Note that nothing above is CSS3. It’s all good ol’ CSS 2.1.</p>
<p>Of course, if you have multiple <code><dd></code>s for every <code><dt></code>, you will need to alter the code a bit. But in that case, this formatting probably won’t be what you want anyway.</p>
<p><strong>Edit:</strong> As <a href="https://twitter.com/codepo8/status/173148263124451328">Christian Heilmann pointed out</a>, HTML3 (!) <a href="http://www.w3.org/MarkUp/html3/deflists.html">used to have a compact attribute</a> on <code><dl></code> elements, which basically did this. It is now obsolete in HTML5, like every other presentational HTML feature.</p>
<p>You can see a live result here:</p>
<iframe style="width: 100%; height: 800px;" src="http://dabblet.com/gist/1901867" width="320" height="240"></iframe>
<p>Tested to work in <strong>IE8+, Chrome, Firefox 3+, Opera 10+, Safari 4+</strong>.</p>
In defense of reinventing wheels2012-04-03T00:00:00Zhttps://lea.verou.me/?p=1642<p>One of the first things a software engineer learns is “don’t reinvent the wheel”. If something is already made, use that instead of writing your own. “Stand on the shoulders of giants, they know what they’re doing better than you”. Writing your own tools and libraries, even when one already exists, is labelled “NIH syndrome” and is considered quite bad. <em><strong>“But what if my version is better?”</strong></em>. Surely, reinventing the wheel can’t be bad when your new wheel improves existing wheel designs, right? Well, not if the software is open source, which is usually the case in our industry. “Just contribute to it” you’ll be told. However, contributing to an open source project is basically teamwork. The success of any team depends on how well its members work together, which is not a given. Sometimes, your vision about the tool might be vastly different from that of the core members and it might be wiser to create your own prototype than to try and change the minds of all these people.</p>
<p>However, Open Source politics is not what I wanted to discuss today. It’s not the biggest potential benefit of reinventing the wheel. <strong>Minimizing overhead is.</strong> You hardly ever need 100% of a project. Given enough time to study its inner workings, you could always delete quite a large chunk of it and it would still fit your needs perfectly. However, the effort needed to do that or to rewrite the percentage you actually need is big enough that you are willing to add redundant code to your codebase.</p>
<p><strong>Redundant code is bad.</strong> It still needs to get parsed and usually at least parts of it still need to be executed. <strong>Redundant code hinders performance.</strong> The more code, the slower your app. Especially when we are dealing with backend code, when every line might end up being executed hundreds or even thousands of times per second. The slower your app becomes, the bigger the need to seriously address performance. The result of that is even more code (e.g. caching stuff) that could have been saved in the first place, by just running what you need. This is the reason software like Joomla, Drupal or vBulletin is so extremely bloated and brings servers to their knees if a site becomes mildly successful. It’s the cost of code that tries to match everyone’s needs.</p>
<p>Performance is not the only drawback involved in redundant code. <strong>A big one is maintainability.</strong> This code won’t only need to be parsed by the machine, it will also be parsed by humans, that don’t know what’s actually needed and what isn’t until they understand what every part does. Therefore, even the simplest of changes become hard.</p>
<p>I’m not saying that using existing software or libraries is bad. I’m saying that it’s always a tradeoff between minimizing effort on one side and minimizing redundant code on the other side. I’m saying that you should <em>consider</em> writing your own code when the percentage of features you need from existing libraries is tiny (lets say less than 20%). It might not be worth carrying the extra 80% forever.</p>
<p>For example, in a project I’m currently working on, I needed to make a simple localization system so that the site can be multilingual. I chose to use JSON files to contain the phrases. I didn’t want the phrases to include HTML, since I didn’t want to have to escape certain symbols. However, they had to include simple formatting like bold and links, otherwise the number of phrases would have to be huge. The obvious solution is <a href="http://daringfireball.net/projects/markdown/">Markdown</a>.</p>
<p>My first thought was to use an existing library, which for PHP is <a href="http://michelf.com/projects/php-markdown/">PHP Markdown</a>. By digging a bit deeper I found that it’s actually considered pretty good and it seems to be well maintained (last update in January 2012) and mature (exists for over 2 years). I should happily use it then, right?</p>
<p>That’s what I was planning to do. And then it struck me: I’m the only person writing these phrases. Even if more people write translations in the future, they will still go through me. So far, the only need for such formatting is links and bold. Everything else (e.g. lists) is handled by the HTML templates. That’s literally <strong>two lines of PHP</strong>! So, I wrote my own function. It’s a bit bigger, since I also added emphasis, just in case:</p>
<pre><code class="language-php">function markdown($text) {
// Links
$text = preg\_replace('@\\\\\[(.+?)\\\\\]\\\\((#.+?)\\\\)@', '<a href="$2">$1</a>', $text);
// Bold
$text = preg\_replace('@(?<!\\\\\\\\)\\\\\*(?<!\\\\\\\\)\\\\\*(.+?)(?<!\\\\\\\\)\\\\\*(?<!\\\\\\\\)\\\\\*@', '<strong>$1</strong>', $text);
// Emphasis
$text = preg\_replace('@(?<!\\\\\\\\)\\\\\*(.+?)(?<!\\\\\\\\)\\\\\*@', '<em>$1</em>', $text);
return $text;
}
</code></pre>
<p>Since PHP regular expressions also support negative lookbehind, I can even avoid escaped characters, in the same line. Unfortunately, since PHP lacks regular expression literals, backslashes have to be doubled (<code>\\</code> instead of <code>\</code> so <code>\\\\</code> instead of <code>\\</code>, which is pretty horrible).</p>
<p>For comparison, PHP Markdown is about 1.7K lines of code. It’s great, if you need the full power of Markdown (e.g. for a comment system) and I’m glad Michel Fortin wrote it. However, for super simple, controlled use cases, is it really worth the extra code? I say no.</p>
<p>Rachel Andrew recently wrote about something tangentially similar, in her blog post titled “<a href="http://www.rachelandrew.co.uk/archives/2012/03/21/stop-solving-problems-you-dont-yet-have/">Stop solving problems you don’t yet have</a>”. It’s a great read and I’d advise you to read that too.</p>
git commit -m "EVERYTHING"2012-04-04T00:00:00Zhttps://lea.verou.me/?p=1670<p>I was working on a project today, when I realized that I had forgotten to commit for days (local only repo). I switched to my terminal, spent at least five minutes trying to decide on the commit message before settling to the completely uninformative “Another commit”. Embarrassed with myself, I shared my frustration with twitter:</p>
<p><a href="https://twitter.com/LeaVerou/status/187533962283986944">https://twitter.com/LeaVerou/status/187533962283986944</a></p>
<p>Immediately, I started getting a flood of suggestions of what that commit message could have been. Some of them were hilarious, some clever and some both. So, I decided I wouldn’t be selfish and I’d share them. Enjoy:</p>
<p><a href="https://twitter.com/codepo8/status/187534089937620994">https://twitter.com/codepo8/status/187534089937620994</a></p>
<p><a href="https://twitter.com/vmasto/status/187534173429448704">https://twitter.com/vmasto/status/187534173429448704</a></p>
<p><a href="https://twitter.com/GovertVerschuur/status/187534218790846466">https://twitter.com/GovertVerschuur/status/187534218790846466</a></p>
<p><a href="https://twitter.com/upperdog_se/status/187534242182467584">https://twitter.com/upperdog_se/status/187534242182467584</a></p>
<p><a href="https://twitter.com/brunoscheele/status/187534245437243392">https://twitter.com/brunoscheele/status/187534245437243392</a></p>
<p><a href="https://twitter.com/idiot/status/187534264265490433">https://twitter.com/idiot/status/187534264265490433</a></p>
<p><a href="https://twitter.com/LukeMaciak/status/187534411955314688">https://twitter.com/LukeMaciak/status/187534411955314688</a></p>
<p><a href="https://twitter.com/jfgen/status/187534471703175168">https://twitter.com/jfgen/status/187534471703175168</a></p>
<p><a href="https://twitter.com/AlexGraul/status/187534893436256259">https://twitter.com/AlexGraul/status/187534893436256259</a></p>
<p><a href="https://twitter.com/captcodemonkey/status/187535071627059201">https://twitter.com/captcodemonkey/status/187535071627059201</a></p>
<p><a href="https://twitter.com/BoltClock/status/187535138266165248">https://twitter.com/BoltClock/status/187535138266165248</a></p>
<p><a href="https://twitter.com/jwkozel/status/187535407397863425">https://twitter.com/jwkozel/status/187535407397863425</a></p>
<p><a href="https://twitter.com/skidding/status/187536889715228672">https://twitter.com/skidding/status/187536889715228672</a></p>
<p><a href="https://twitter.com/omgmog/status/187537072213598209">https://twitter.com/omgmog/status/187537072213598209</a></p>
<p><a href="https://twitter.com/stevehickeydsgn/status/187538150007123969">https://twitter.com/stevehickeydsgn/status/187538150007123969</a></p>
<p><a href="https://twitter.com/_dte/status/187538777441452032">https://twitter.com/_dte/status/187538777441452032</a></p>
<p><a href="https://twitter.com/nathandim/status/187538945305870336">https://twitter.com/nathandim/status/187538945305870336</a></p>
<p><a href="https://twitter.com/jwkozel/status/187539130585063424">https://twitter.com/jwkozel/status/187539130585063424</a></p>
<p><a href="https://twitter.com/LukeMaciak/status/187539160851165184">https://twitter.com/LukeMaciak/status/187539160851165184</a></p>
<p><a href="https://twitter.com/croncobaurul/status/187539379428925442">https://twitter.com/croncobaurul/status/187539379428925442</a></p>
<p><a href="https://twitter.com/MayaPosch/status/187539668793950208">https://twitter.com/MayaPosch/status/187539668793950208</a></p>
<p><a href="https://twitter.com/OllyHodgson/status/187539827493834752">https://twitter.com/OllyHodgson/status/187539827493834752</a></p>
<p><a href="https://twitter.com/eternicode/status/187540177734991873">https://twitter.com/eternicode/status/187540177734991873</a></p>
<p><a href="https://twitter.com/mrtnrsl/status/187540310094643201">https://twitter.com/mrtnrsl/status/187540310094643201</a></p>
<p><a href="https://twitter.com/kioopi/status/187540668728619008">https://twitter.com/kioopi/status/187540668728619008</a></p>
<p><a href="https://twitter.com/streetpc/status/187541599532744704">https://twitter.com/streetpc/status/187541599532744704</a></p>
<p><a href="https://twitter.com/GNi33/status/187541700091195392">https://twitter.com/GNi33/status/187541700091195392</a></p>
<p><a href="https://twitter.com/jo%5C_Osiah/status/187541784870666241">https://twitter.com/jo\_Osiah/status/187541784870666241</a></p>
<p><a href="https://twitter.com/dalecruse/status/187541971949203460">https://twitter.com/dalecruse/status/187541971949203460</a></p>
<p><a href="https://twitter.com/jordanpittman/status/187542101658058753">https://twitter.com/jordanpittman/status/187542101658058753</a></p>
<p><a href="https://twitter.com/michelegera/status/187542353446313984">https://twitter.com/michelegera/status/187542353446313984</a></p>
<p><a href="https://twitter.com/StuRobson/status/187543502538809344">https://twitter.com/StuRobson/status/187543502538809344</a></p>
<p><a href="https://twitter.com/gmoulin_dev/status/187544693779873792">https://twitter.com/gmoulin_dev/status/187544693779873792</a></p>
Pure CSS scrolling shadows with background-attachment: local2012-04-26T00:00:00Zhttps://lea.verou.me/?p=1684<p>A few days ago, the incredibly talented <a href="https://twitter.com/kizmarh">Roman Komarov</a> posted an experiment of his with <a href="http://kizu.ru/en/fun/shadowscroll/">pure CSS “scrolling shadows”</a>. If you’re using Google Reader, you are probably familiar with the effect:</p>
<p><a href="https://lea.verou.me/2012/04/background-attachment-local/images/scrolling-shadows.png"><img src="https://lea.verou.me/2012/04/background-attachment-local/images/scrolling-shadows.png" alt="Screenshot demonstrating the “scrolling shadows” in Google Reader" title="“Scrolling shadows” in Google Reader" /></a></p>
<p>In Roman’s experiment, he is using absolutely positioned pseudoelements to cover the shadows (which are basically radial gradients as background images), taking advantage of the fact that when you scroll a scrollable container, its background does not scroll with it, but absolutely positioned elements within do. Therefore, when you scroll, the shadows are no longer obscured and can show through. Furthermore, these pseudoelements are linear gradients from white to transparent, so that these shadows are uncovered smoothly.</p>
<p>When I saw Roman’s demo, I started wondering whether this is possible with no extra containers at all (pseudoelements included). It seemed like a perfect use case for <code>background-attachment: local</code>. Actually, it was the first real use case for it I had ever came up with or seen.</p>
<h2 id="%E2%80%9Cbackground-attachment%E2%80%A6-what%3F-i-only-know-scroll-and-fixed!%E2%80%9D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/04/background-attachment-local/#%E2%80%9Cbackground-attachment%E2%80%A6-what%3F-i-only-know-scroll-and-fixed!%E2%80%9D">“background-attachment… what? I only know scroll and fixed!”</a></h2>
<p><code>scroll</code> and <code>fixed</code> were the only values for background-attachment back in the days of CSS 2.1. <code>scroll</code> is the initial value and positions the background relative to <em>the element it’s applied on</em>, whereas <code>fixed</code> positions it relative to <em>the viewport</em>, resulting in the background staying in place when the page was scrolled. As a result of these definitions, when only a part of the page was scrollable (e.g. a <code><div></code> with <code>overflow: auto</code>), its background did not scroll when the container itself was scrolled.</p>
<p>In <a href="http://w3.org/TR/css3-background">Backgrounds & Borders Level 3</a>, a new value was added to lift this restriction: <a href="http://www.w3.org/TR/css3-background/#local0"><code>local</code></a>. When <code>background-attachment: local</code> is applied, the background is positioned relative to the element’s contents. The result is that it scrolls when the element is scrolled. This is not a new feature, it has been with us since <a href="http://www.w3.org/TR/2005/WD-css3-background-20050216/#the-background-attachment">the first drafts of Backgrounds and Borders 3 in 2005</a> (of course, implementations took some more time, starting from 2009).</p>
<p>If the way it works seems unclear, play a bit with this dabblet that demonstrates all three values (your browser needs to support all three of course):</p>
<iframe style="width: 100%; height: 600px;" src="https://dabblet.com/gist/2494566" width="320" height="240"></iframe>
<h2 id="%E2%80%9Cok%2C-i-get-it.-back-to-the-scrolling-shadows-please%3F%E2%80%9D" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/04/background-attachment-local/#%E2%80%9Cok%2C-i-get-it.-back-to-the-scrolling-shadows-please%3F%E2%80%9D">“Ok, I get it. Back to the scrolling shadows please?”</a></h2>
<p>Basically, the idea was to convert these absolutely positioned pseudoelements into background layers that have background-attachment: local applied. I tried it, it worked and helped reduce the code quite a lot. Here’s the dabblet with it:</p>
<iframe style="width: 100%; height: 600px;" src="https://dabblet.com/gist/2462915" width="320" height="240"></iframe>
<p>The drawback of this is that it reduces browser support a bit. Roman’s original experiment might even work in IE8, if the gradients are converted into images (gradients are not essential for the functionality). When you rely on background-attachment: local, you reduce browser support to <strong>IE9+, Safari 5+, Chrome</strong> and <strong>Opera</strong>. Firefox is the most notable absentee of that list, you can vote on <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=483446">bug #483446</a> if you’re interested in getting them to implement it (edit: Firefox supports this now [2013]).</p>
<p>However, browser support is not that important, since the effect degrades very gracefully. On browsers that don’t support this, you just get no shadow. ;)</p>
How I got into web development — the long version2012-05-01T00:00:00Zhttps://lea.verou.me/?p=1703<p>I’m often asked how I got into web development, especially from people that haven’t met many women in the field. Other times it’s people with little kids and they are asking for guidance about how to steer them into programming. I promised them that I would write a long post about it at some point, and now that I’m in the verge of some big changes in my life, I’ve started reflecting on the fascinating journey that got me here.</p>
<p><a href="http://rmurphey.com/">Rebecca Murphey</a> <a href="http://rmurphey.com/blog/2012/03/25/girls-and-computers/">wrote something similar</a> a while back (albeit much shorter and less detailed), and I think it would be nice if more people in the field started posting their stories, especially women. I sure would find them interesting and if you give it a shot, you’ll see it’s quite enjoyable too. I sure had a blast writing this, although it was a bit hard to hit the “Publish” button afterwards.</p>
<p>Keep in mind that this is just my personal story (perhaps in excruciating detail). <strong>I’m not going to attempt to give any advice, and I’m not suggesting that my path was ideal.</strong> I’ve regretted some of my decisions myself, whereas some others proved to be great, although they seemed like failures at the time. I think I was quite lucky in how certain things turned out and I thank the <a href="http://en.wikipedia.org/wiki/Flying_Spaghetti_Monster">Flying Spaghetti Monster</a> daily for them.</p>
<p><strong>Warning:</strong> This is going to be a very long read (over 3000 words) and there is no tl;dr.</p>
<h2 id="childhood-(1986-1998)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/#childhood-(1986-1998)">Childhood (1986-1998)</a></h2>
<p>I was born on June 13th, 1986. I grew up in a Greek island called <a href="http://en.wikipedia.org/wiki/Lesbos">Lesbos</a> (yes, <a href="http://en.wikipedia.org/wiki/Lesbian#Origin_and_transformation_of_the_term">the island where the word “lesbian” comes from</a>, in case you were wondering), in the small town of <a href="http://maps.google.com/maps?q=kalloni+lesbos&um=1&ie=UTF-8&hq=&hnear=0x14ba921f491ef7a1:0xdc822d7cec0b916e,Kalloni,+Greece&ei=ysyfT5CZMYep4gSExvWeAw&sa=X&oi=geocode_result&ct=title&resnum=1&ved=0CCQQ8gEwAA">Kalloni</a>. I didn’t have a computer as a kid, but I always loved making things. I had no siblings, so my childhood was mostly spent playing solitarily with paper, fabric, staples, scissors and the like. I was making all kinds of stuff: Little books, wallets, bags, pillows, anything I could come up with that was doable with my limited set of tools and materials. I also loved drawing. I had typical toys as well (legos, dolls, playmobil, cars, teddy bears) but the prevailing tendency in my childhood was <em>making stuff</em>. I wasn’t particularly interested in taking things apart to see how they worked, I just liked making new things.</p>
<p>I had never used a computer until I was around 10. We spent Christmas with an uncle of mine and his family in Athens. That uncle was working at Microsoft Hellas, and had a Windows 95 machine in his apartment. I got hooked from the first moment I used that computer. I didn’t do anything particularly interesting in it, just played around with MS Paint and some other equally mundane applications. However, for me it was so fascinating that I spent most of my Christmas vacation that year exploring Windows 95.</p>
<p>After I returned to Lesbos, I knew I badly wanted a computer for myself. However, computers were quite expensive back then, so I didn’t get one immediately, even though my family was quite well off. My father started taking me to his job’s offices on weekends, and I spent hours every time on a Windows 3.1 machine, exploring it, mostly drawing on its paint app.</p>
<p>In 1997, my mother finally bought me a computer. It cost around 700K drachmas (around €2000?) which was much more at the time than it is today. It was a Pentium MMX at 233MHz with 32MB of RAM and a 3.1GB hard drive, which was quite good at the time. I was so looking forward for it to arrive, and when it did, I spent every afternoon using it, from the moment I got back from school, until late at night. The only times I didn’t use my computer was when I was reading computer books or magazines or studying for school. In a year, I had become quite proficient about how its OS worked (Windows 95), editing the registry, trying to learn DOS (its command line). I also exercised my creativity by making magazines and newspapers in Microsoft Word. I’m quite surprised I didn’t break it, even though I was experimenting with anything I could get my cursor on.</p>
<p>Unfortunately, my computer fascination was largely solitary. There were no other geeks in my little town I could relate to, which I guess made me even more of an introvert. The only people reading my MS Word-generated newspaper were me and a friend of mine. During my years in Lesbos, I only met 2 other kinda geeky kids, and we didn’t really hit it off. One of them was living too far, the other was kind of annoying. :P The former however gave me his fonts, which I was really grateful for. I loved fonts. I didn’t have any typographic sophistication, so I loved about every font, but I remember desperately wanting to make my own. Unfortunately, I never pursued that, as I couldn’t find any font creation software until very recently.</p>
<p>In late 1997, we visited some relatives in a NYC suburb to spend Christmas there. It was my first time in the US and I fell in love with the place. My uncle, knowing my computer obsession took me to a big computer store called CompUSA. I was like a kid in a candy store! The software that caught my eye the most was called “<a href="http://en.wikipedia.org/wiki/Clickteam">Mutimedia Fusion</a>”. It was a graphical IDE that allowed you to make applications (mostly games and screensavers, but you could potentially make anything) without writing any code. The thought processes involved were the same as in programming, but instead of typing commands, you picked them from menus or wrote mathematical expressions through a GUI. You could even go online and get new plugins that added functionality, but my access to the internet in my little town was very limited.</p>
<p>I got super excited. The idea of being able to make my very own programs, was too good to be true. I convinced my mother to buy it for me and thankfully, she did. For the year that followed, my afternoons and weekends became way more creative. I wasn’t interested in making games, but more in utility applications. Things that were going to be useful for my imaginary users. My biggest app back then was something that allowed you to draw different kinds of grids (from horizontal and vertical grids to simple 3d-like kinds of grids), with different parameters, or even mix them together and overlay them over an image. Anything that combined programming with graphics was doubly fascinating for me.</p>
<p>My access to the internet was limited, so I couldn’t share my creations with anybody. What kept me going was the idea that if I make something amazing, it will get popular and people will use it. I had no idea how that would happen, but it was useful as a carrot in front of me that made me constantly strive to improve. We had dial-up, but due to technical issues, I could only connect about 10% of the times I tried it, and even then I had to keep it short as it was quite expensive. I spent my limited time online downloading plugins for Multimedia Fusion, searching anything I could come up with in Altavista and perusing IRC chatrooms with Microsoft Comic Chat.</p>
<h2 id="adolescence-(1998-2004)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/#adolescence-(1998-2004)">Adolescence (1998-2004)</a></h2>
<p>After a year of making applications with Multimedia Fusion, I wanted something more flexible and powerful. I wanted to finally learn a programming language. My Microsoft uncle sent me a free copy of Visual Studio, so I was trying to decide which “Visual Whatever” language was best to start with. Having read that C++ was “teh pro stuff”, I got a book about Visual C++. Unfortunately, I couldn’t understand much. I decided that it was probably too early for me and C++, so I got a Visual Basic 6 book. It was about 10cm thick, detailing everything you could ever possibly want to learn about Visual Basic. Thankfully, Visual Basic didn’t prove so hard, so I started with it, making small apps and finally ported my grid application from Multimedia Fusion to Visual Basic 6.</p>
<p>I had a very fun and creative 3 years, full of new knowledge and exercise for the mind. Unfortunately, when I reached 15, I realized that boys in my little town weren’t really into geeky girls. I decided that if I wanted a boyfriend, I should quit programming (if any geeky teenage girls are reading this: Just be patient. It gets better, you can’t imagine how much). It “helped” that my computer was broken during the summer and I had to wait for it to come back, so I had to find other things to do in the meantime.</p>
<p>Unable to code, I pursued other geeky interests, such as mobile phones and mathematics, which I guess shows that no matter how much you try, you can’t escape who you are. In retrospect, this helped me, as I got some pretty good distinctions in the various stages of the national mathematical competitions, up to 2nd place nationally for two years in a row (these competitions had 4 stages. I failed the preliminary contest for the Balkan Mathematical Olympiad, so I never went there.). I was fascinated by Number Theory and started wanting to become a mathematician, rather than a programmer. Sometime around then I also moved from my small town to Athens, which I wanted to do since childhood.</p>
<p>When the time of career decisions came, I chickened out. I knew that if I became a mathematician and failed at research, I would end up teaching mathematics in a high school. I didn’t want that, so I picked a “safer” career path. Since my grades were very good, I went to study Electrical and Computer Engineering, which is a profession held in very high esteem in Greece, about as much as lawyers and doctors. I told myself that I would probably find it interesting, as it would involve lots of mathematics and programming. I was wrong.</p>
<h2 id="adulthood-(2004-today)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/#adulthood-(2004-today)">Adulthood (2004-Today)</a></h2>
<p>I was away from Athens, in a city that most Greeks love (Thessaloniki). However, I found it cold, gray, old and with hordes of cockroaches. I hated it with a passion. I also hated my university. It involved little coding and little theoretical Mathematics, the kind that I loved. Most of it was physics and branches of Mathematics I didn’t like, such as linear algebra. It only had two coding courses, both of which were quite mundane and lacked any kind of creativity. Moreover, most of my fellow students had perviously wanted to become doctors and failed medical school so they just went for the next highly respected option. They had no interest in technology and their main life goals were job security, making money and be respected. I felt more lonely than ever. After the first semester, I slowly stopped going to lectures and eventually gave up socializing with them. Not going to lectures is not particularly unusual for a university student in Greece. Most Greeks do it after a while, since attendance is not compulsory and Greek universities are free (as in beer). As long as you pass your exams every semester and do your homework, you can still get a degree just fine.</p>
<p>During my first summer as a university student, we decided with my then boyfriend to make an online forum. We were both big fans of online forums and we wanted to make something better. He set up the forum software in an afternoon (using <a href="http://www.simplemachines.org/">SMF</a>) and then we started customizing it. I didn’t know much about web development back then, so I constrained myself to helping with images and settings. After 2 months, the forum grew to around 200 members, and we decided to switch to the more professional (and costly) forum software, <a href="https://www.vbulletin.com/">vBulletin</a>. It was probably too early, but the signs were positive, so we thought better earlier than later.</p>
<p>The migration took 2-3 days of nonstop work, during which we took turns in sleeping and worked the entire time that we were awake. We wanted everything to be perfect, even the forum theme should be as similar to the old one as possible. I had a more involved role in this, and I even started learning a bit of PHP while trying to install some “mods” (modifications to the vBulletin source code that people posted). Due to my programming background, I caught up with it quite easily and after a few months, I was the only one fiddling with code on the website.</p>
<p>I was learning more and more about PHP, HTML, CSS and (later) JavaScript. That online forum was my primary playground, where I put my newly acquired knowledge into practice. Throughout these years, I released quite a few of <a href="http://www.vbulletin.org/forum/member.php?u=106158&hacksort=title#hacks">my own vBulletin mods</a>, many of which are still in use in vBulletin forums worldwide. Having spent so many years making apps that nobody used, I found it fascinating that you can make something and have people use it only a few hours later.</p>
<p>By the end of 2005, I started undertaking some very small scale client work, most (or all) of which doesn’t exist anymore. I was not only interested in code, but also in graphic design. I started buying lots of books, both about the languages involved and graphic design principles. The pace of learning new things back then was crazy, almost on par with my early adolescence years.</p>
<p>In late 2006, I decided I couldn’t take it any more with my university. I had absolutely no interest in Electrical Engineering, and my web development work had consumed me entirely. I didn’t want to give up on higher education, so I tried to decide where I should switch to. Computer Science was the obvious choice, but having grown up with civil engineer parents, I didn’t want to give up on engineering just yet (strangely, CS is not considered engineering in Greece, it’s considered a science, kinda like Mathematics). I also loved graphic design, so I considered going to a graphic design school, but there are no respected graphic design universities in Greece and I wasn’t ready to study abroad. I was also in a long term relationship in Greece, which I didn’t want to give up on.</p>
<p>I decided to go with Architecture, although I had no interest in buildings. The idea was that it bridges engineering and art, so it would satisfy both of my interests. Unfortunately, since I hadn’t taken drawing classes in high school, I had to take the entire national university placement exams (Πανελλήνιες), again, including courses I aced the first time, such as Mathematics. I was supposed to spend the first half of 2007 preparing for these exams, but instead I spent most of it freelancing and learning more about web development. I did quite well on the courses I had been previously examined on (although not as good as the first time), but borderline failed freehand drawing. Passing freehand drawing was a requirement for Architecture, so that was out of the question now. This seemed like a disaster at the time, but in retrospect, I’m very grateful to the grader that failed me. I would’ve been utterly miserable in Architecture.</p>
<p>Not wanting to go back to EE, I took a look at my options. My mother suggested Computer Science and even though I was still a bit reluctant, I put it in my application. I picked a <a href="http://www.cs.aueb.gr/">CS school</a> that seemed more programming-oriented, as I didn’t want to have many physics, computer architecture and circuits courses again. When the results came out, I had been placed there. It turned out to be one of my best decisions. I could get good grades on most of the courses with hardly any studying, as I knew lots of the stuff already. I also learned a bunch of useful new things. I can’t say that everything I learned was useful for my work, but it was enough to make it worth it.</p>
<p>In mid 2007, the online forum we built had grown quite a lot. We decided to make a company around it, in order to be able to accept more high-end advertising. We had many dreams about expanding what it does, most of which never got materialized. In 2008, after a long time of back and forth, we officially registered <a href="http://fresset.gr/">a company</a> for it so I stopped freelancing and focused solely on that.</p>
<p>It wasn’t easy, but eventually it started generating a very moderate income. I decided to start a Greek blog to post about my CSS and JS discoveries, but it didn’t go very well. After a dozen posts or so, I decided to close it down, and start a new one, in English this time. It turned out that developers abroad were more interested in what I had to say, so I got my first conference invitation in 2010, to speak in a new Polish conference called <a href="http://2010.front-trends.com/">Front-Trends</a>. When I got the invitation email, I couldn’t believe my eyes. Why would someone want <strong>me</strong> to speak at a conference? I wasn’t that good! How would I speak in front of all these people? It even crossed my mind that it might be a joke, but they had confirmed speakers like Douglas Crockford, Jake Archibald, Jeremy Keith and Paul Bakaus. I told my inner shy self to shut up, and enthusiastically agreed to speak there.</p>
<p>I spent the 8 months until that conference stressing about my presentation. I had never been to a conference outside Greece, and the only Greek conference I had attended was a graphic design one. I had only spoken once before, to an audience of around 30 people in a barcamp-style event. I decided that I didn’t want my first web development conference to be the one I speak at, so I bought a ticket for <a href="http://fronteers.nl/congres/2010">Fronteers 2010</a>. It had a great line-up and was quite affordable (less than €300 for a ticket). I convinced 3 of my friends to come with me (for vacation), and we shared a quadruple hotel room, so the accommodation ended up not costing too much either.</p>
<p>It was an amazing experience that I will never forget. I met people I admired and only knew through their work online. It was the first time in my life that I was face to face with people that really shared the same interests. I even met my partner to date there. Until today, Fronteers is my favorite conference. Partly because it was my first, partly because it’s a truly great conference with a very strong sense of community.</p>
<p>There was a talk or two at Fronteers that year, which were criticized for showing things that most people in the audience already knew. This became my worst fear about giving talks. Until today, I always try to add nuggets of more advanced techniques in my talks, to avoid getting that kind of reaction, and it works quite well. I remember going back home after Fronteers and pretty much changing all my slides for my upcoming talk. I trashed my death-by-powerpoint kind of slides and my neat bulleted lists and made a web-based slideshow with interactive examples for everything I wanted to show.</p>
<p>I was incredibly nervous before and during my Front-Trends talk, so I kept mumbling and confusing my words. However, despite what I thought throughout, the crowd there loved it. The comments on twitter were enthusiastic! Many people even said it was the best talk of the conference.</p>
<p>That first talk was the beginning of a roller-coaster that I just can’t describe. I started getting more invitations for talks, articles, workshops and many other kinds of fascinating things. I met amazing people along the way. Funny, like-minded, intelligent people. To this day, I think that getting in this industry has been the best thing in my life. I have experienced no sexism or other discrimination, nothing negative, just pure fun, creativity and a sense that I belong in a community with like-minded people that understand me. It’s been great, and I hope it continues to be like this for a very long time. Thank you all.</p>
Text masking — The standards way2012-05-04T00:00:00Zhttps://lea.verou.me/?p=1721<p>As much as I like .net magazine, I was recently outraged by their “<a href="http://www.netmagazine.com/tutorials/texturise-web-type-css">Texturizing Web Type</a>” article. It features a way to apply a texture to text with <code>-webkit-mask-image</code>, presenting it as an experimental CSS property and misleading readers. There are even -moz-, -o- and -ms- prefixes for something that is not present in any specification, and is therefore unlikely to ever be supported by any non-WebKit browser, which further contributes to the misdirection. A while back, <a href="http://www.alistapart.com/articles/every-time-you-call-a-proprietary-feature-css3-a-kitten-dies/">I wrote</a> about how detrimental to our work and industry such proprietary features can be.</p>
<p>A common response to such complaints is that they are merely philosophical and who cares if the feature works right now and degrades gracefully. This argument could be valid for some cases, when the style is just a minor, gracefully degrading enhancement and no standards compliant alternative is present (for example, I’ve used <code>::-webkit-scrollbar</code> styles myself). However, this is not the case here. We have had <a href="http://www.w3.org/TR/2001/WD-SVG11-20011030/" title="Warning: This is a very early version of the SVG 1.1 spec. For reference, use the latest one.">a standards compliant alternative for this for the past 11 years</a> and it’s called SVG. It can also do much more than masking, if you give it a chance. Here’s an example of texturized text with SVG:</p>
<iframe style="width: 100%; height: 600px;" src="http://dabblet.com/gist/2594420" width="320" height="240"></iframe>
<p><strong>Edit:</strong> Thanks to <a href="https://twitter.com/devongovett/status/198513261333848064">@devongovett’s improvements</a>, the code is now simpler & shorter.</p>
<p>Yes, the syntax might be more unwieldy but it works in a much wider range of browsers: <strong>Chrome, Safari, Firefox, IE9, Opera</strong>. Also, it’s trivial to make a script that generates the SVG markup from headings and applies the correct measurements for each one. When WebKit fixes <a href="https://bugs.webkit.org/show_bug.cgi?id=65344">this bug</a>, we can even move the pattern to a separate SVG file and reference it from there.</p>
<p>In case you’re wondering about semantics, the <code><svg></code> element is considered “flow content” and is therefore allowed in heading elements. Also, even if search engines don’t understand inline SVG, they will just ignore the tags and still see the content inside the <code><text></code> element. Based on that, you could even make it degrade gracefully in IE8, as long as you include the HTML5 fix for the <code><svg></code> element. Then the CSS rules for the typography will still apply. You’ll just need to conditionally hide the <code><image></code>, since IE8 displays a broken image there (a little known fact is that, in HTML, <code><image></code> is basically equivalent to <code><img></code>, so IE8 treats it as such) .</p>
<p><em>Credits to <a href="https://twitter.com/dstorey">David Storey</a>’s <a href="http://my.opera.com/dstorey/blog/using-svg-masks-for-cut-out-text-effects">original example</a> that inspired this.</em></p>
Poll: ¿Is animation-direction a good idea?2012-05-06T00:00:00Zhttps://lea.verou.me/?p=1736<h2 id="%C2%BFanimation-direction%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/#%C2%BFanimation-direction%3F">¿animation-direction?</a></h2>
<p>Lets assume you have a CSS animation for <code>background-color</code> that goes from a shade of yellow (#cc0) to a shade of blue (#079) and repeats indefinitely. The code could be something like this:</p>
<pre><code class="language-css">@keyframes color {
from { background: #cc0 }
to { background: #079 }
}
div {
animation: color 3s infinite;
}
</code></pre>
<p>If we linearize that animation, the progression over time goes like this (showing 3 iterations):</p>
<p><a href="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/images/Screen-shot-2012-05-05-at-19.53.52-.png"><img src="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/images/Screen-shot-2012-05-05-at-19.53.52-.png" alt="" title="No animation-direction specified" /></a></p>
<p>As you can see, the change from the end state to the beginning state of a new iteration is quite abrupt. You could change your keyframes to mitigate this, but there’s a better way. A property with the name <code>animation-direction</code> gives a degree of control on the direction of the iterations to smooth this out. It also reverses your timing functions, which makes it even smoother.</p>
<p>In early drafts, only the values <code>normal</code> and <code>alternate</code> were allowed. <code>normal</code> results in the behavior described above, whereas <code>alternate</code> flips every other iteration (the 2nd, the 4th, the 6th and so on), resulting in a progression like this (note how the 2nd iteration has been reversed):</p>
<p><a href="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/images/Screen-shot-2012-05-05-at-20.04.21-.png"><img src="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/images/Screen-shot-2012-05-05-at-20.04.21--1024x80.png" alt="" title="animation-direction: alternate;" /></a></p>
<p>The latest draft also adds two more values: <code>reverse</code> which reverses <strong>every</strong> iteration and <code>alternate-reverse</code>, which is the combination of both <code>reverse</code> and <code>alternate</code>. Here is a summary of what kind of progression these four values would create for the animation above:</p>
<p><a href="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/images/Screen-shot-2012-05-05-at-20.19.05-.png"><img src="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/images/Screen-shot-2012-05-05-at-20.19.05--1024x362.png" alt="" title="animation-direction values" /></a></p>
<h2 id="the-problem" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/#the-problem">The problem</a></h2>
<p>I was excited to see that <code>reverse</code> and <code>alternate-reverse</code> were finally added to the spec, but something in the syntax just didn’t click. I initially thought the reason was that these four values essentially set 2 toggles:</p>
<ul>
<li>¿Reverse all iterations? yes/no</li>
<li>¿Reverse even iterations? yes/no</li>
</ul>
<p>so it’s pointless cognitive overhead to remember four distinct values. <a href="http://lists.w3.org/Archives/Public/www-style/2012Apr/0799.html">I proposed that they should be split in two keywords</a> instead, which would even result to <a href="http://lists.w3.org/Archives/Public/www-style/2012Apr/0804.html">a simpler grammar</a> too.</p>
<p>The proposal was well received by one of the co-editors of the animations spec (<a href="https://twitter.com/sgalineau">Sylvain Galineau</a>), but there was a dilemma as to whether mixing <code>normal</code> with <code>alternate</code> or <code>reverse</code> would make it easier to learn or more confusing. <strong>This is a point where your opinion would be quite useful.</strong> Would you expect the following to work, or would you find them confusing?</p>
<ul>
<li><code>animation-direction: normal alternate;</code> /* Equivalent to animation-direction: alternate; */</li>
<li><code>animation-direction: alternate normal;</code> /* Equivalent to animation-direction: alternate; */</li>
<li><code>animation-direction: normal reverse;</code> /* Equivalent to animation-direction: reverse; */</li>
<li><code>animation-direction: reverse normal;</code> /* Equivalent to animation-direction: reverse; */</li>
</ul>
<h2 id="a-better-(%3F)-idea" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/#a-better-(%3F)-idea">A better (?) idea</a></h2>
<p>At some point, in the middle of writing this blog post (I started it yesterday), while gazing at the graphic above, I had a lightbulb moment. ¡These values are not two toggles! All four of them control one thing: <strong>which iterations are reversed</strong>:</p>
<ul>
<li><code>normal</code> reverses no iterations</li>
<li><code>reverse</code> reverses all iterations</li>
<li><code>alternate</code> reverses even iterations</li>
<li><code>alternate-reverse</code> reverses odd iterations</li>
</ul>
<p>The reason it’s so confusing and it took me so long to realize myself, is that the mental model suggested by these keywords is detached from the end result, especially in the case of <code>alternate-reverse</code>. You have to realize that it works as if both <code>alternate</code> and <code>reverse</code> were applied in sequence, so <code>reverse</code> first reverses <strong>all</strong> iterations and then <code>alternate</code> reverses the <strong>even</strong> ones. Even iterations are reversed <strong>twice</strong>, and are therefore equivalent to the original direction. This leaves the odd ones as being reversed. It’s basically a double negative, making it hard to visualize and understand.</p>
<p>I thought that a property that would reflect this in a much more straightforward way, would be <code>animation-reverse</code> (or <code>animation-iteration-reverse</code>), accepting the following values:</p>
<ul>
<li><code>none</code> (equivalent to animation-direction: normal)</li>
<li><code>all</code> (equivalent to animation-direction: reverse)</li>
<li><code>even</code> (equivalent to animation-direction: alternate)</li>
<li><code>odd</code> (equivalent to animation-direction: alternate-reverse)</li>
</ul>
<p>Not only this communicates the end result much better, but it’s also more extensible. For example, if in the future it turns out that reversing every 3rd iteration is a common use case, it will be much easier to add expressions to it, similar to the ones :nth-child() accepts.</p>
<p>I knew before <a href="http://lists.w3.org/Archives/Public/www-style/2012May/0185.html">proposing it</a> that it’s too late for such drastic backwards-incompatible changes in the <a href="http://www.w3.org/TR/css3-animations/">Animations module</a>, however I thought it’s so much better that it’s worth fighting for. After all, <code>animation-direction</code> isn’t really used that much in the wild.</p>
<p>Unfortunately, it seems that only me and Sylvain thought it’s better, and even he <a href="http://lists.w3.org/Archives/Public/www-style/2012May/0188.html">was reluctant to support the change</a>, due to the backwards compatibility issues. So, I started wondering if it’s really as much better as I think. <strong>¿What are your thoughts?</strong> ¿Would it make it simpler for you to understand and/or teach? Author feedback is immensely useful for standardization, so please, <strong>¡voice your opinion!</strong> Even without justifying it if you don’t have the time or energy. Gathering opinions is incredibly useful.</p>
<h2 id="tl%3Bdr" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/#tl%3Bdr">TL;DR</a></h2>
<ol>
<li>¿Is <code>alternate reverse</code> and <code>reverse alternate</code> (either would be allowed) a better value for <code>animation-direction</code> over <code>alternate-reverse</code>?</li>
<li>¿If so, should redundant combinations of <code>normal</code> with <code>alternate</code> or <code>reverse</code> also be allowed, such as <code>normal alternate</code>?</li>
<li>¿Or maybe we should ditch it altogether and replace it with <code>animation-reverse</code>, accepting values of <code>none</code>, <code>all</code>, <code>even</code>, <code>odd</code>?</li>
</ol>
<p><strong>Side note:</strong> If you’re wondering about the flipped question and exclamation marks (¿¡) it’s because <a href="https://twitter.com/LeaVerou/status/198556042387390464">I believe they improve the usability of the language</a> if widely adopted, so <a href="https://twitter.com/LeaVerou/status/198559059346063360">I’m doing my part</a> for it ;) And no, I don’t speak Spanish.</p>
Teaching: General case first or special cases first?2012-05-09T00:00:00Zhttps://lea.verou.me/?p=1780<p>A common dilemma while teaching (I’m not only talking about teaching in a school or university; talks and workshops are also teaching), is whether it’s better to first teach some easy special cases and then generalize, or first the general case and then present special cases as merely shortcuts.</p>
<p>I’ve been revisiting this dilemma recently, while preparing the slides for <a href="http://lea.verou.me/speaking/">my upcoming regular expressions talks</a>. For example: Regex quantifiers.</p>
<h2 id="1.-general-rule-first%2C-shortcuts-after" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/teaching-general-case-first-or-special-cases-first/#1.-general-rule-first%2C-shortcuts-after">1. General rule first, shortcuts after</a></h2>
<p>You can use {m,n} to control how many times the preceding group can repeat (m = minimum, n = maximum). If you omit n (like {m,}) it’s implied to be infinity (=“at least m times”, with no upper bound).</p>
<ul>
<li m="">{m, m} can also be written as</li>
<li>{0,1} can also be written as ?</li>
<li>{0,} can also be written as *</li>
<li>{1,} can also be written as +</li>
</ul>
<h3 id="advantages-%26-disadvantages-of-this-approach" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/teaching-general-case-first-or-special-cases-first/#advantages-%26-disadvantages-of-this-approach">Advantages & disadvantages of this approach</a></h3>
<ul>
<li>Harder to understand the general rule, so the student might lose interest before moving on to the shortcuts</li>
<li>After understanding the general rule, all the shortcuts are then trivial.</li>
<li>If they only remember one thing, it will be the general rule. That’s good.</li>
</ul>
<h2 id="2.-special-cases-first%2C-general-rule-after" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/teaching-general-case-first-or-special-cases-first/#2.-special-cases-first%2C-general-rule-after">2. Special cases first, general rule after</a></h2>
<ul>
<li>You can add ? after a group to make it optional (it can appear, but it may also not).</li>
<li>If you don’t care about how many times something appears (if at all), you can use *.</li>
<li>If you want something to appear at least once, you can use +</li>
<li n="">If you want something to be repeated exactly n times, you can use</li>
<li>If you want to set specific upper and lower bounds, you can use {m,n}. Omit the n for no upper bound.</li>
</ul>
<h3 id="advantages-%26-disadvantages-of-this-approach-1" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/teaching-general-case-first-or-special-cases-first/#advantages-%26-disadvantages-of-this-approach-1">Advantages & disadvantages of this approach</a></h3>
<ul>
<li>Easy to understand the simpler special cases, building up student interest</li>
<li>More total effort required, as every shortcut seems like a separate new thing until you get to the general rule</li>
<li>Special cases make it easier to understand the general rule when you get to it</li>
</ul>
<h2 id="what-usually-happens" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/05/teaching-general-case-first-or-special-cases-first/#what-usually-happens">What usually happens</a></h2>
<p>In most cases, educators seem to favor the second approach. In the example of regex quantifiers, pretty much every regex book or talk explains the shortcuts first and the general rule afterwards. In other disciplines, such as Mathematics, I think both approaches are used just as often.</p>
<p>What do you think? Which approach do you find easier to understand? Which approach do you usually employ while teaching?</p>
Hacking lookahead to mimic intersection, subtraction and negation2012-05-13T00:00:00Zhttps://lea.verou.me/?p=1784<p><em><strong>Note:</strong> To understand the following, I expect you to know how regex lookahead works. If you don’t, <a href="http://www.regular-expressions.info/lookaround.html">read about it first</a> and return here after you’re done.</em></p>
<p>I was quite excited to discover this, but to my dismay, <a href="https://twitter.com/slevithan/status/201340048317227008">Steven Levithan assured me</a> it’s actually well known. However, I felt it’s so useful and underdocumented (the only references to the technique I could find was several StackOverflow replies) that I decided to blog about it anyway.</p>
<p>If you’ve been using regular expressions for a while, you surely have stumbled on a variation of the following problems:</p>
<ul>
<li><strong>Intersection</strong>: “I want to match something that matches pattern A AND pattern B” <em>Example: A password of at least 6 characters that contains at least one digit, at least one letter and at least one symbol</em></li>
<li><strong>Subtraction</strong>: “I want to match something that matches pattern A but NOT pattern B” <em>Example:</em> <em>Match any integer that is not divisible by 50</em></li>
<li><strong>Negation</strong>: “I want to match anything that does NOT match pattern A” <em>Example: Match anything that does NOT contain the string “foo”</em></li>
</ul>
<p>Even though in ECMAScript we can use the caret (^) to negate a character class, we cannot negate anything else. Furthermore, even though we have the pipe character to mean OR, we have nothing that means AND. And of course, we have nothing that means “except” (subtraction). All these are fairly easy to do for single characters, through character classes, but not for entire sequences.</p>
<p>However, we can mimic all three operations by taking advantage of the fact that lookahead is zero length and therefore does not advance the matching position. We can just keep on matching to our heart’s content after it, and it will be matched against the same substring, since the lookahead itself consumed no characters. For a simple example, the regex <code>/^(?!cat)\w{3}$/</code> will match any 3 letter word that is NOT “cat”. This was a very simple example of <strong>subtraction</strong>. Similarly, the solution to the subtraction problem above would look like <code>/^(?!\d+[50]0)\d+$/</code>.</p>
<p>For <strong>intersection</strong> (AND), we can just chain multiple positive lookaheads, and put the last pattern as the one that actually captures (if everything is within a lookahead, you’ll still get the same boolean result, but not the right matches). For example, the solution to the password problem above would look like <code>/^(?=.*\d)(?=.*[a-z])(?=.*[\W_]).{6,}$/i</code>. Note that if you want to support IE8, you have to take <a href="http://blog.stevenlevithan.com/archives/regex-lookahead-bug">this bug</a> into account and modify the pattern accordingly.</p>
<p><strong>Negation</strong> is the simplest: We just want a negative lookahead and a .+ to match anything (as long as it passes the lookahead test). For example, the solution to the negation problem above would look like <code>/^(?!.*foo).+$/</code>. Admittedly, negation is also the least useful on its own.</p>
<p>There are caveats to this technique, usually related to what actually matches in the end (make sure your actual capturing pattern, outside the lookaheads, captures the entire thing you’re interested in!), but it’s fairly simple for just testing whether something matches.</p>
<p>Steven Levithan took lookahead hacking to the next level, by using something similar to <a href="http://blog.stevenlevithan.com/archives/mimic-conditionals">mimic conditionals</a> and <a href="http://blog.stevenlevithan.com/archives/mimic-atomic-groups">atomic groups</a>. Mind = blown.</p>
Why I bought a high-end MacBook Air instead of the Retina MBP2012-06-17T00:00:00Zhttps://lea.verou.me/?p=1803<p><a href="https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/images/my-mba.jpg"><img src="https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/images/my-mba-300x224.jpg" alt="" title="My new MacBook Air" /></a>After the WWDC keynote, I was convinced I would buy a new MacBook Air. I was looking forward to any announcements about new hardware during the event, as my 13" 2010 MacBook Pro (Henceforth abbreviated as MBP) was becoming increasingly slow and dated. Also, I wanted to gift my MBP to my mother, who is currently using a horrible tiny Windows XP Netbook and every time I see her struggling to work on it, my insides hurt. All tweets about my shopping plans, or, later, about my new toy (I bought it yesterday) were met with surprise and bewilderment: I was repeatedly bombarded with questions asking why I’m not getting a Retina MacBook Pro, over and over again. The fact that I paid about $2200 + tax for it (it’s the best 13" Air you can currently get) made it even more weird: <strong>If you could afford that, why wouldn’t you possibly get the Retina MBP at the exact same price?</strong></p>
<p>At first, I tried to reply with individual tweets to everyone that asked. Then I got tired of that and started replying with links to the first tweets, then I decided to write a blog post. So, here are my reasons:</p>
<h3 id="portability" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/#portability">Portability</a></h3>
<p>I travel a lot. For me, it’s very important to be able to use my laptop in a cramped airplane seat, or while standing in a line. You can’t really do that with a 15" MacBook Pro, even with the new slimmer design. I wanted to be able to quickly pull it out of my tote bag with one hand, hold it with said hand and quickly look up something with the other hand. Usage scenarios of that sort are just unthinkable for big laptops. Of course, portability is not the only thing that matters, as I only use one laptop as my main work machine. Having two machines, one for portability and one for “real work”, always seemed to me like more hassle than it’s worth. So, a 11" MacBook Air was also out of the question. Which brings us to the middle ground of a 13" laptop. All my laptops had always been around 13". It’s a perfect trade-off between power and portability and I don’t wish to change that any time soon. It was quite simple: The 13" Air is more portable than my MBP. The 15" Retina MBP was less portable. I needed more portability than I had, not less.</p>
<h3 id="i-saw-the-retina-mbp-and-wasn%E2%80%99t-too-impressed" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/#i-saw-the-retina-mbp-and-wasn%E2%80%99t-too-impressed">I saw the Retina MBP and wasn’t too impressed</a></h3>
<p>When I first went to the Apple Store to buy the MacBook Air, I saw the new Retina display. I even managed to use it a bit, despite the swarm of fellow geeks nerdgasming uncontrollably around it. I won’t lie: I was tempted at first. The display is very crisp indeed, although the difference between icons that were not updated for the Retina is quite obvious, especially next to their accompanying text (which is always crisp, since text is vector-based). I started being unsure about my decision, as <a href="http://www.stubbornella.org/content/">Nicole Sullivan</a> can attest (she was with me). And then it dawned on me: Hey, I should see the MacBook I was planning to buy in person too. Maybe its screen is also quite crisp. Maybe the difference won’t even be that noticeable. I was right: My simple, untrained eyes could not really tell the difference. MacBook Airs have a decently crisp screen. Of course, if you put them next to each other, I’d imagine the difference would be fairly obvious. But who does that?</p>
<p>However, my impression still wasn’t sufficient to make a decision. I’ve learned not to trust my unreliable human senses too much. I needed numbers. Calculating the actual DPI of a monitor is actually fairly simple: All you need is the <a href="http://en.wikipedia.org/wiki/Pythagorean_theorem">Pythagorean theorem</a> you learned in school, to calculate the hypotenuse of the screen in pixels, and then divide that number by the length of the diagonal in inches. The result will be the number of pixels per inch, commonly (and slightly incorrectly) referred to as DPI (PPI is more correct). If you know basic JavaScript, you don’t even need a calculator, just your ol’ trusty console.</p>
<p>I even wrote a handy function that does it for me:</p>
<p>function dpi(w,h,inches) { return Math.round(Math.sqrt(w*w + h*h)/inches) }</p>
<p>For the 13" MacBook Air, the DPI is:</p>
<p>> dpi(1440, 900, 13.3)
128</p>
<p>For the Retina MBP, it’s:</p>
<p>> dpi(2880, 1800, 15.4)
221</p>
<p>220 DPI is certainly higher than 130, but it’s not the kind of eyegasm-inducing difference I experienced when I moved from an iPhone 3G to an iPhone 4 (the difference there was 163 DPI vs 326 DPI).</p>
<h3 id="i-don%E2%80%99t-want-to-distance-myself-too-much-from-the-average-web-user" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/#i-don%E2%80%99t-want-to-distance-myself-too-much-from-the-average-web-user">I don’t want to distance myself too much from the average web user</a></h3>
<p>It happens more than we like to admit: We get cool new hardware, and eventually we’re carried away and think most web users are close to our level. We start designing for bigger and bigger resolutions, because it’s hard to imagine that some people are still on 1024x768. We code to better CPUs, because it’s hard to imagine how crappy computers many of our target users have. We don’t care about bandwidth and battery, because they aren’t a concern for most of us. Some of us will realize before launching, during a very painful testing session, some others will only realize after the complaints start pouring in. It’s the same reason a website always looks and works better in the browser its developers use, even though almost always it gets tested in many others.</p>
<p>We like to think we’re better than that, that we always test, that we never get carried away, but in most cases we are lying to ourselves. So, even though I recognize that I probably have much better hardware than most web users, I consciously try to avoid huge resolutions as I know I’ll get carried away. I try to keep myself as close to the average user as I can tolerate. Using IE9 on a 1024x768 resolution would be over that threshold, but not using a Retina display is easily tolerable.</p>
<h3 id="that%E2%80%99s-all-folks" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/#that%E2%80%99s-all-folks">That’s all folks</a></h3>
<p>Hope this makes sense. Hopefully, it might help others trying to decide between the two. I sure am very happy so far with my new Air :)</p>
So, you’ve been invited to speak2012-06-25T00:00:00Zhttps://lea.verou.me/?p=1824<p>I’ve been lucky enough to be invited to do <a href="http://lea.verou.me/speaking/#past">about 25 talks</a> over the course of the past few years and I have quite <a href="http://lea.verou.me/speaking/#upcoming">a few upcoming gigs</a> as well, most of them at international conferences around Europe and the US. Despite my speaking experience, I’m still very reluctant to call myself a “professional speaker” or even a “speaker” at all. In case you <a href="https://twitter.com/leaverou">follow me on twitter</a>, you might have noticed that my bio says “Often pretends to be a speaker”, and that captures exactly how I feel. I’m not one of those confident performers that don’t just present interesting stuff, but also can blurt jokes one after the other, almost like stand-up comedians and never backtrack or go “ummm”. I greatly admire these people and I aspire to become as confident as them on stage one day. People like <a href="https://twitter.com/aral">Aral Balkan</a>, <a href="https://twitter.com/codepo8">Christian Heilmann</a>, <a href="https://twitter.com/stubbornella">Nicole Sullivan</a>, <a href="https://twitter.com/jaffathecake">Jake Archibald</a> and many others. Unlike them, I often backtrack mid-sentence, say a lot of "ummmm"s and sometimes talk about stuff that was going to be later in my slides, all of which are very awkward.</p>
<p>However, I’ve reached the conclusion that I must be doing something right. I do get a lot of overwhelmingly positive feedback after almost every talk, even by people I admire in the industry. I don’t think I’ve ever gotten a negative comment for a talk, even in cases that I thought I had screwed up. Naturally, after all these conferences, I’ve attended a lot of technical talks myself, and I’ve gathered some insight on what constitutes a technical talk the audience will enjoy. I’ve been pondering to write a post with advice about this for a long time, but my lack of confidence about my speaking abilities put me off the task. However, since people <a href="https://twitter.com/briangarcia/status/213321684890025984">seem</a> <a href="https://twitter.com/yaypie/status/213307187475386369">to</a> <a href="https://twitter.com/miketaylr/status/213299054166286337">consider</a> <a href="https://twitter.com/aral/status/210720110535651330">me</a> <a href="https://twitter.com/aral/status/209580131470147585">good</a>, I figured it might help others doing technical talks as well.</p>
<p>All of the following are rules of thumb. You have to keep in mind that there are exceptions to every single one, but it’s often quicker and more interesting to talk in absolutes. I will try to stay away from what’s already been said in other similar articles, such as “tell a story” or “be funny” etc, not because it’s bad advice, but because a) I’m not really good at those so I prefer to let others discuss them and b) I don’t like repeating stuff that’s already been said numerous times before. I will try to focus on what I do differently, and why I think it works. It might not fit your style and that’s ok. Audiences like a wide range of presentation styles, otherwise I’d be screwed, as I don’t fit the traditional “good speaker” profile. Also, it goes without saying that some of my advice might be flat out wrong. I’m just trying to do pattern recognition to figure out why people like my talks. That’s bound to be error-prone. My talks might be succeeding in spite of X and not because of it.</p>
<h3 id="1.-do-something-unique" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/#1.-do-something-unique">1. Do something unique</a></h3>
<p>There are many nice talks with good minimal slides (almost everyone has read <a href="http://www.presentationzen.com/">Presentation Zen</a> by now), funny pictures, good content and confident presenters. In fact, they dominate almost every conference I’ve been at. You can always become a good speaker by playing it safe, and many famous speakers in the industry have earned their fame by doing so. There is absolutely nothing wrong with that. However, to stand out doing that kind of talk, you need to be <strong>really, really good</strong>. Hats off to the speakers that managed to stand out doing talks like that, because it means they are truly amazing.</p>
<p>However, if you, like me, fear that your speaking skills are not yet close to that caliber, you need to figure out something else that sets you apart. Something that will make your talk memorable. <strong>We see a lot of innovation in our discipline, but it’s limited to the scripts and apps we write. Why not to our presentations as well?</strong> Do something different, and make it your thing, your “trademark” way of presenting.</p>
<p>For me, that was the embedded demos in my slides. I usually have a small text field where I write code, and something (often the entire slide or text field itself) that displays the outcome. This lets the attendees see not just the end result, but also the intermediate states until we get there, which often proves out to be enlightening. It also makes the slide quite flexible, as I can always show something extra if I have the time.</p>
<p>Of course, it also means that things might (and if you talk often enough, will at some point) go wrong. To mitigate this to a degree, I try to keep demos small, with a sensible starting state, so that I won’t have to write a lot of code. Which brings us to the next point.</p>
<h3 id="2.-never-show-a-lot-of-code-on-a-slide" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/#2.-never-show-a-lot-of-code-on-a-slide">2. Never show a lot of code on a slide</a></h3>
<p>I have a theory: Attendees’ understanding of code decreases exponentially as the lines of simultaneously displayed code increase. Avoid showing many lines of code at once like the plague. Although I’ve shown up to 10 lines of code on a single slide (maybe even more), I usually try to keep it well below five. Ideally less than three even. If you absolutely <strong>must</strong> present more code, try to use a technique to make the audience understand it by chunks, so that they still only have to process very little code at any given time.</p>
<p>One technique I use for that is showing little code at first, and writing the rest on stage, gradually, explaining the steps as I go. When that isn’t possible or it doesn’t make sense (for example when there is no visual result to see), I try to show parts of the code step by step, explaining what everything does as it appears. This doesn’t necessarily mean showing one line at a time. For example, if you are showing a JS function, it makes sense to show the closing brace at the same time as the opening brace and not at the end. Show the elements of the code in the order you would write them in a top-down implementation, not by pure source order (although in some cases those two may coincide). To make this more clear, here’s an <a href="http://talks.verou.me/polyfilling-the-gaps/#slide45">example slide</a> where I used this technique. It’s from my “Polyfilling the gaps” talk at JSConf EU 2011, one of the very few talks of mine that had no live coding.</p>
<p>Also, it goes without saying that if you have to present a lot of code at once, syntax highlighting is a must. Comments aren’t: That’s what you are there for. Comments just add visual clutter and make it harder for people to interpret the actual code. Also, while explaining the code, try to somehow highlight the part you’re currently describing, even if your method is as rudimentary as selecting the text. Otherwise, if someone misses a sentence, they will completely lose track.</p>
<h3 id="3.-ides-are-not-good-presentation-tools" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/#3.-ides-are-not-good-presentation-tools">3. IDEs are not good presentation tools</a></h3>
<p>I’ve seen this so many times: Someone wants to live demo a technology and fires up their IDE or text editor and starts coding. The audience tries to follow along at first, but at some point almost always gets lost. While these tools are great for programming, they are not presentation tools. They have a lot of distracting clutter, very small text and require you to show parts of the code that aren’t really relevant. They also require you to switch applications to toggle between the code and result, which disrupts the flow of your presentation.</p>
<p>In addition, the probability you will make a mistake increases exponentially with the amount of code you write, both in real life and especially in presentations where you are also nervous. Then the audience is stuck with an embarrassed presenter trying to find what’s wrong for five minutes, until someone from the audience decides to put them out of their misery and shout that a closing parenthesis is missing on line 25.</p>
<p>That’s why live coding has gotten a bad reputation over the years.</p>
<p>As you’ve probably figured from tip #1, I’m not against live coding. Done well, it can really help the audience learn. However, if not done properly, it can end up completely wrecking a talk. Even if you absolutely have to use an external tool, try to make the experience as smooth as possible:</p>
<ul>
<li>Hide any toolbars, sidebars, panels. If your editor doesn’t allow you to hide everything that isn’t relevant, use another editor.</li>
<li>Make the text BIG. If possible, as big as the text in your slides. Remember: Text in slides is big, because you need even the attendees sitting in the back rows to still be able to read it. Why is it that this simple consideration seems to escape so many presenter minds when they switch from slides to code?</li>
<li>If parts of the code are needed but not relevant (e.g. CSS files in a JavaScript talk), put them in separate files and reference them. Try to minimize the code you will actually show as much as possible, and then even more.</li>
<li>If applicable, use <a href="http://livereload.com/">LiveReload</a> and have the browser window and code editor side by side.</li>
</ul>
<h3 id="4.-don%E2%80%99t-aim-to-beginners-(only)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/#4.-don%E2%80%99t-aim-to-beginners-(only)">4. Don’t aim to beginners (only)</a></h3>
<p>Some of the nastiest criticism I’ve seen against people’s talks was that they were too elementary. Getting feedback like that has almost become a phobia of mine. Of course, it’s always better if your entire audience is at the same level, and you are fully aware what that level is. However, that’s almost never the case, so you will have to err on one side. Do your best to cater to the median, but when you have to err, err on the side of more advanced content. A somewhat selfish reason would be that <strong>when people find your talk too elementary, they will blame you; when they find it too advanced, they will blame themselves.</strong> However, it’s not just covering your ass, it’s better for your audience as well. Someone who didn’t learn anything new gets absolutely nothing out of your talk (unless it’s an interesting performance on its own, e.g. so funny it could have been stand up comedy for geeks). A person that learned many things but didn’t understand some of the more advanced concepts will still have gotten a lot out of it.</p>
<p><strong>If someone learns a useful thing or two from your talk, that’s what they’ll remember.</strong> Even if the rest of the talk was elementary or too advanced for them, they will walk out with a positive impression, thinking “I learned something today!”. Even if most of your talk is elementary, try to sneak in some more advanced or obscure bits, that not many people know.</p>
<p>My favorite approach to cater for a diverse audience with very different levels of experience, is to pick a narrow topic, start from the very basics and work my way up to more advanced concepts. This way everyone learns something, and nobody feels intimidated. On the flip side, if you are in a multi-track conference, this also limits the potential audience that might come to your talk.</p>
<h3 id="5.-eat-your-own-dog-food" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/#5.-eat-your-own-dog-food">5. Eat your own dog food</a></h3>
<p>I’m a huge fan of HTML-based (or SVG-based) slideshows. I’ve always been, since my first talk. It’s a technology you’re already accustomed to, so you can do amazing things with it. You can write scripts that demonstrate the concepts you describe in some visual way, you can do live demos, you can embed iframes of other people’s demos, you know how to style it much better than you likely know how to use Keynote. Yes, if you’re used to traditional presentation tools, it might be hard at first. Many features you’ve been taking for granted will be missing. Styling is not visual, there are no presenter notes, no next slide preview, no automatic adjustment to the projector resolution to name a few. But what you gain in potential and expressiveness, are totally worth the trade-off. Also, rather than having the talk prep keep you from writing code and becoming better at what you do, it will now actually contribute to it! It’s also a great chance to try experimental stuff, as it’s going to be run in a very controlled environment.</p>
<p>You don’t even need to write your own presentation framework if you don’t want to. There are a ton available now, such as <a href="https://github.com/LeaVerou/CSSS">my own CSSS</a>, <a href="http://bartaz.github.com/impress.js/">impress.js</a> and <a href="http://en.wikipedia.org/wiki/Web-based_slideshow">many others</a>.</p>
<h3 id="6.-involve-the-audience" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/#6.-involve-the-audience">6. Involve the audience</a></h3>
<p>There is an old Chinese proverb that goes like:</p>
<blockquote>
<p>Tell me, and I’ll forget Show me, and I’ll remember Involve me, and I’ll understand</p>
</blockquote>
<p>I’ve noticed that audiences respond extremely well to talks that attempt to involve them. <a href="http://seb.ly/">Seb-Lee Delisle</a> gave a talk at Fronteers 2011 where he involved the audience by ideas like demonstrating Web Sockets through making their iPhones flash in such a way that he could create light patterns with the audience. Even though some of the demos failed (I think something crashed, don’t remember very well), the audience <strong>loved every bit</strong>. I’ve rarely seen people that excited about a talk.</p>
<p>Involving the audience was something I wanted to do for a while. In my recent Regular Expressions talks, I had a series of small “challenges” where the audience tried to write a regexp to match a certain set of strings as quickly as possible and tweet it. I provided a link to <a href="http://leaverou.github.com/regexplained/">an app I made especially for that</a>. The person who got most regexes right (or more right than others) won a book. This was also very well received and lots of positive feedback mentioned it. When it feels like a game, learning is much more fun.</p>
Important -prefix-free update2012-07-24T00:00:00Zhttps://lea.verou.me/?p=1845<p>Those of you that have been following and/or using my work, are surely familiar with <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a>. Its promise was to let you write DRY code, without all the messy prefixes, that would be standards-compliant in the future (which is why I’m always against adding proprietary features in it, regardless of their popularity). The way <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a> works is that it feature tests which CSS features are available <strong>only</strong> with a prefix, and then adds the prefix in front of their occurences in the code. Nothing will happen if the feature is supported both with and without a prefix or if it’s not supported at all.</p>
<p>This worked well when browsers implementations aren’t significantly different from the unprefixed, standard version. It also works fine when the newer and the older version use incompatible syntaxes. For example, direction keywords in gradients. The old version uses <code>top</code> whereas the new version uses <code>to bottom</code>. If you include both versions, the cascade does its job and ignores the latter version if it’s not supported:</p>
<pre><code class="language-css">background: linear-gradient(top, white, black);
background: linear-gradient(to bottom, white, black);
</code></pre>
<p>However, when the same syntax means different things in the older and the newer version, things can go horribly wrong. Thankfully, this case is quite rare. A prime example of this is linear gradient angles. <code>0deg</code> means a horizontal (left to right) gradient in prefixed linear-gradients and a vertical (bottom to top) gradient in unprefixed implementations, since they follow the newer <a href="http://www.w3.org/TR/css3-images/">Candidate Recommendation</a> rather than the old draft. This wasn’t a problem when every browser supported only prefixed gradients. However, now that <a href="http://blogs.msdn.com/b/ie/archive/2012/06/25/unprefixed-css3-gradients-in-ie10.aspx">IE10</a> and <a href="http://hacks.mozilla.org/2012/07/aurora-16-is-out/">Firefox 16</a> are unprefixing their gradients implementations, it was time for me to face the issue I was avoiding ever since I wrote <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a>.</p>
<p>The solution I decided on is consistent with <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a>’s original promise of allowing you to write mostly standards-compliant code that will not even need <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a> in the future. Therefore, <strong>it will assume that your gradients use the newer syntax</strong>, and if only a prefixed implementation is available, it will convert the angles to the legacy definition. This means that <strong>if you update <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a> on a page that includes gradients coded with the older definition, they might break</strong>. However, <strong>they would break anyway</strong> in modern browsers, so the sooner the better. Even if you weren’t using <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a> at all, and had written all the declarations by hand before the angles changed, you would still have to update your code. Unfortunately, that’s the risk we all take when using experimental features like CSS gradients and I think it’s worth it.</p>
<p><a href="http://leaverou.github.com/prefixfree/">-prefix-free</a> will not take care of any other syntax changes, since when the syntaxes are incompatible, you can easily include both declarations. The angles hotfix was included out of necessity because there is no other way to deal with it.</p>
<p>Here’s a handy JS function that converts older angles to newer ones:</p>
<pre><code class="language-javascript">function fromLegacy(deg) { return Math.abs(deg-450) % 360 }
</code></pre>
<p>You can read more about the changes in gradient syntax in <a href="http://blogs.msdn.com/b/ie/archive/2012/06/25/unprefixed-css3-gradients-in-ie10.aspx">this excellent IEblog article</a>.</p>
<p>In addition to this change, a new feature was added to <a href="http://leaverou.github.com/prefixfree/">-prefix-free</a>. If you ONLY want to use the prefixed version of a feature, but still don’t want to write out of all the prefixes, you can just use <code>-*-</code> as a prefix placeholder and it will be replaced with the current browser’s prefix on runtime. So, if you don’t want to change your angles, you can just prepend <code>-*-</code> to your linear-gradients, like so:</p>
<pre><code class="language-css">background: -*-linear-gradient(0deg, white, black);
</code></pre>
<p>However, it’s a much more futureproof and standards compatible solution to just update your angles to the new definition. You know you’ll have to do it at some point anyway. ;)</p>
<p><strong>Edit:</strong> Although -prefix-free doesn’t handle syntax changes in radial gradients, since the syntaxes are mutually incompatible, you may use this little PrefixFree plugin I wrote for the <a href="http://lea.verou.me/css3patterns/">CSS Patterns Gallery</a>, which converts the standard syntax to legacy syntax when needed:</p>
<pre><code class="language-javascript">StyleFix.register(function(css, raw) {
if (PrefixFree.functions.indexOf('radial-gradient') > -1) {
css = css.replace(/radial-gradient\(([a-z-\s]+\s+)?at ([^,]+)(?=,)/g, function($0, shape, center){
return 'radial-gradient(' + center + (shape? ', ' + shape : '');
});
}
return css;
});
</code></pre>
<p>Keep in mind however that it’s very crude and not very well tested.</p>
Introducing Prism: An awesome new syntax highlighter2012-07-31T00:00:00Zhttps://lea.verou.me/?p=1841<p><a href="http://prismjs.com/"><img src="https://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/images/Screen-Shot-2012-07-31-at-18.33.58--300x158.png" alt="" title="Screenshot from Prism’s homepage" /></a>For the past three weeks, on and off, I’ve been working on releasing <a href="http://dabblet.com/">Dabblet</a>’s syntax highlighter as standalone, since many people had requested it. Zachary Forrest <a href="https://twitter.com/zdfs/statuses/217834980871639041">suggested the name “Prism”</a> and I liked it so much I decided to go with it, even though there is <a href="https://wiki.mozilla.org/Prism">an abandoned Mozilla project with the same name</a>. I ended up refactoring and extending it so much that I will need to backport it to Dabblet one of these days! This doesn’t mean I bloated it, the core is still a tiny 1.5KB minified & gzipped. It just means it’s more awesome. :)</p>
<p><a href="http://prismjs.com/"><img src="https://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/images/Screen-Shot-2012-07-31-at-18.31.22-.png" alt="" title="Prism’s first themes" /></a></p>
<h2 id="seriously%3F-the-world-needs-another-syntax-highlighter%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/#seriously%3F-the-world-needs-another-syntax-highlighter%3F">Seriously? The world needs another syntax highlighter?</a></h2>
<p>In certain ways, Prism is better than any other syntax highlighter I’ve seen:</p>
<ul>
<li>It’s tiny. The core is only 1.5KB minified & gzipped, which can go up to 2KB with the currently available language definitions (CSS, Markup and JS). But many other highlighters are also small, so read on.</li>
<li>It’s <strong>incredibly extensible</strong>. Not only it’s easy to add new languages (that’s a given with every syntax highlighter these days), but also to <strong>extend existing ones</strong>. Most importantly, it supports <strong>plugins</strong>, through <strong>hooks strategically placed in its source</strong>. There are <a href="http://prismjs.com/plugins/">a few plugins already available</a>, and it’s <a href="http://prismjs.com/extending.html#writing-plugins">really easy to write your own</a>. I haven’t seen any other highlighter that supports plugins.</li>
<li>It <strong>encourages good author practices</strong>. Other highlighters encourage or even force you to use elements that are semantically wrong, like <code><pre></code> (on its own) or <code><script></code>. Prism forces you to use the only semantically correct element for marking up code: <code><code></code>. On its own for inline code, or inside a <code><pre></code> for blocks of code. In addition, the code language is declared through <a href="http://www.w3.org/TR/html5/the-code-element.html#the-code-element">the way recommended in the HTML5 draft</a>: through a language-xxxx class.</li>
<li>One of its best features: <strong>The language definition is inherited</strong>. This means that if multiple code snippets have the same language, you can just define it once, in one of their common ancestors. Obviously, if you define a language on the <code><body></code> element, you’ve essentially declared a default language for the entire document.</li>
<li><strong>It looks good</strong>. All three of its existing themes. Most people wanted me to release Dabblet’s highlighter because they found other highlighters (including their themes) quite ugly.</li>
<li>It supports <strong>parallelism through Web Workers</strong>, for better performance in certain cases.</li>
<li>It <strong>doesn’t force you to use any Prism-specific markup</strong>, not even a Prism-specific class name, only standard markup you should be using anyway. So, you can just try it for a while, remove it if you don’t like it and leave no traces behind.</li>
</ul>
<p>However, there are some limitations too:</p>
<ul>
<li>Pre-existing HTML in the code block will be stripped off. However, there are plugins for <a href="http://prismjs.com/plugins/autolinker/">links</a> and <a href="http://prismjs.com/plugins/line-highlight">highlighting certain lines</a>.</li>
<li>I decided not to support IE8. Prism won’t break on IE8, it just won’t work. I don’t think many people using IE8 and below are able to read code, so I don’t see the point.</li>
</ul>
<p>Enjoy: <a href="http://prismjs.com/">prismjs.com</a></p>
<p>If you like this project, don’t forget to <a href="https://twitter.com/prismjs">follow @prismjs on Twitter</a>!</p>
<p>I’ll soon update this blog to use Prism in the code examples as well.</p>
lea@w3․org2012-08-01T00:00:00Zhttps://lea.verou.me/?p=1866<p><a href="https://lea.verou.me/2012/08/lea-at-w3-org/images/Screen-Shot-2012-08-01-at-14.55.40-.png"><img src="https://lea.verou.me/2012/08/lea-at-w3-org/images/Screen-Shot-2012-08-01-at-14.55.40-.png" alt="" title=":)" /></a>In my recent post describing <a href="http://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/">how I got into web development</a> I wrote that I’m in the verge of some big changes in my life. The main one of them starts tomorrow. As of tomorrow, the above will be my professional email. Yes, you guessed it right; I’m joining the <a href="http://w3.org/">W3C</a> <a href="http://www.w3.org/People/">team</a>! Yes, the same <a href="http://w3.org/">W3C</a> you all know and love :) I decided to title this blog post with it, as I like how a 10 letter string manages to neatly summarize so much.</p>
<p>Working at W3C had been a dream of mine ever since I learned what a web standard is. As you probably know if you’ve been following my work, I’m a strong believer in open web standards. Even though proprietary technology might offer some short term benefits, in the long run only open standards can allow the Web to reach its full potential.</p>
<p>I’d like to especially thank the two people below (in chronological order). If it wasn’t for them, this dream would have never materialized:</p>
<ul>
<li><a href="http://oli.jp/">Oli Studholme</a>: I still remember our IRC conversation back in January. I was telling him how much I’d love to work for W3C, but “I’m not that good”. He repeatedly encouraged me to contact W3C and express my interest, despite my strong reluctance to do so. “Don’t be like the 15 year old boy that is too shy to ask the girl out” was the argument that finally convinced me. He even asked around to find which person I should contact.</li>
<li><a href="http://schepers.cc/">Doug Schepers</a>: If it wasn’t for Doug’s heroic efforts, this would have never happened. He believed in me from the start and did everything he could to for this to go through. He spent an incredible amount of time trying to help me, although I repeatedly bombarded him with a cornucopia of silly questions. :) Over the course of these 6 months, he didn’t just become a colleague, but also a friend.</li>
</ul>
<p>Thank you both. I’m deeply grateful.</p>
<p>I will be part of the <a href="http://www.w3.org/community/devrel/">W3C developer relations</a> and <a href="http://www.w3.org/community/webed/">web education</a> efforts, working a lot with Doug (aka <a href="http://twitter.com/shepazu">@shepazu</a>). In practice, this means:</p>
<ul>
<li>Help developers understand where standards are headed, and solicit early feedback on upcoming features.</li>
<li>Help Working Groups understand what developers need.</li>
<li>Help plan W3C developer events, including conferences</li>
<li>Speaking about open web technologies at conferences and other events</li>
<li>Writing articles and documentation about open web technologies</li>
<li>Making demos and tools that demonstrate and help authors understand web standards</li>
</ul>
<p>In addition, I will be helping with the design of many W3C-related things, as I will be the only designer at W3C.</p>
<p>As you can see I’ll be wearing many hats, which is exactly what I love about this role! I had many tempting offers from big US companies that offered salaries with more digits and a lot of perks. However, my heart wanted W3C and this role was practically tailored to my talents and interests.</p>
<p>I’m honored to be a part of <a href="http://w3.org/">W3C</a> and I’m looking forward to helping out.</p>
<p><code><voice type="fangirl"></code>I have to admit I’m also really looking forward to meeting Sir <a href="http://www.w3.org/People/all#timbl">Tim Berners-Lee</a> in person! :D<code></voice></code></p>
Dive deep into CSS3 (and Bolognese!) in Bologna, Italy2012-09-13T00:00:00Zhttps://lea.verou.me/?p=1921<p><a href="https://lea.verou.me/2012/09/dive-deep-into-css3-in-bologna-italy/images/spk_verou.png"><img src="https://lea.verou.me/2012/09/dive-deep-into-css3-in-bologna-italy/images/spk_verou.png" alt="" title="Illustration of me!" /></a>I don’t usually like to advertise my talks or workshops through blog posts, and even though I’ve given a lot, I’ve only posted about less a handful. However, this one is special: <strong>It might be my last</strong>. Don’t get me wrong: I LOVE giving workshops, teaching people new things and seeing them put them in use right away is fantastic. However, I also find them incredibly exhausting. Speaking for an entire day (or sometimes two!) is pretty much the most tiring thing I’ve done. So, given <a href="http://lea.verou.me/2012/08/lea-at-w3-org/">my new job at W3C</a>, I’m not sure if I will do one again. Of course, it goes without saying that I will still do plenty of talks! :)</p>
<p>The last workshop on my schedule is in <a href="http://2012.fromthefront.it/">FromTheFront conference in Bologna, Italy</a> on <strong>September 20th</strong> (in 7 days!). There are still some spots available, so <a href="http://2012.fromthefront.it/index.html#cart">grab yours while you still can</a>! It only costs <strong>€329.00</strong>. It will be very hands-on, with interactive exercises that help you gain first-hand experience with small but advanced use cases. It will not be your usual CSS3-overview kind of workshop. Instead, we will dive really deep into a handful of CSS3 aspects that I think are most useful for your everyday work.</p>
<p>While you’re at it, I’d also recommend getting a conference ticket as well. The <a href="http://2012.fromthefront.it/speakers.html">line-up</a> has some excellent speakers and it’s only €110 more, so totally worth it!</p>
<p>Apologies that my last two blog posts were personal, the next one will be more technical: I have a very useful tool in the pipeline that I’m gonna release soon ;)</p>
Easy color contrast ratios2012-10-14T00:00:00Zhttps://lea.verou.me/?p=1941<p><a href="https://lea.verou.me/2012/10/easy-color-contrast-ratios/images/Screen-Shot-2012-10-14-at-06.08.41-.png"><img src="https://lea.verou.me/2012/10/easy-color-contrast-ratios/images/Screen-Shot-2012-10-14-at-06.08.41--300x254.png" alt="" title="Screenshot with semi transparent background" /></a>I was always interested in accessibility, but I never had to comply with any guidelines before. <a href="http://lea.verou.me/2012/08/lea-at-w3-org/">At W3C</a>, accessibility is considered very important, so everything we make needs to pass <a href="http://www.w3.org/TR/WCAG/">WCAG 2.0</a> AA level. Therefore, I found myself calculating color contrast ratios very frequently. It was a very enlightening experience. I used to think that WCAG-mandated contrast ratios were too restrictive and basically tried to force you to use black and white, a sentiment shared by many designers I’ve spoken to. Surprisingly, in practice, I found that in most cases they are very reasonable: When a color combination doesn’t pass WCAG, it usually *is* hard to read. After all, the possible range for a contrast ratio is 1-21 but only ratios lower than 3 don’t pass WCAG AA (4.5 if you have smaller, non-bold text). So, effectively 90% of combinations will pass (82.5% for smaller, non-bold text).</p>
<p>There are <a href="http://snook.ca/technical/colour_contrast/colour.html">plenty</a> <a href="http://www.colorsontheweb.com/colorcontrast.asp">of</a> <a href="http://juicystudio.com/services/luminositycontrastratio.php">tools</a> out there for this. However, I found that my workflow for checking a contrast ratio with them was far from ideal. I had to convert my CSS colors to hex notation (which I don’t often use myself anymore), check the contrast ratio, then adjust the colors as necessary, covert again etc. In addition, I had to adjust the lightness of the colors with a blindfold, without being able to see the difference my adjustments would make to the contrast ratio. When using semi-transparent colors, it was even worse: Since WCAG only describes an algorithm for opaque colors, all contrast tools only expect that. So, I had to calculate the resulting opaque colors after alpha blending had taken place. After doing that for a few days, I got so fed up that I decided to make <a href="https://contrast-ratio.com/">my own tool</a>.</p>
<p>In addition, I discovered that there was no documented way of calculating the contrast ratio range that can be produced with a semi-transparent background, so I came up with an algorithm (after many successive failures to find the range intuitively), <a href="http://lists.w3.org/Archives/Public/w3c-wai-ig/2012OctDec/0011.html">published it in the w3c-wai-ig mailing list</a> and used the algorithm in my app, effectively making it the first one that can accept semi-transparent colors. If your math is less rusty than mine, I’d appreciate any feedback on my reasoning there.</p>
<p>Below is a list of features that make this tool unique for calculating color contrast ratios:</p>
<ul>
<li>Accepts any CSS color the browser does, not just hex colors. To do this, it defers parsing of the color to the browser, and queries the computed style, which is always rgb() or rgba() with 0-255 ranges which be parsed much more easily than the multitude of different formats than modern browsers accept (and the even more that are coming in the future).</li>
<li>Updates as you type, when what you’ve typed can be parsed as a valid CSS color.</li>
<li>Accepts semi transparent colors. For semi-transparent backgrounds, the contrast ratio is presented with an error margin, since it can vary depending on the backdrop. In that case, the result circle will not have a solid background, but a visualization of the different possible results and their likelihood (see <a href="https://lea.verou.me/2012/10/easy-color-contrast-ratios/images/Screen-Shot-2012-10-14-at-06.08.41-.png">screenshot</a>).</li>
<li>You can share your results by sharing the URL. The URL hashes have a reasonable structure of the form #foreground-on-background, e.g. <a href="http://leaverou.github.com/contrast-ratio/#black-on-yellow">#black-on-yellow</a> so you can even adjust the URL as a form of input.</li>
<li>You can adjust the color by incrementing or decrementing its components with the keyboard arrow keys until you get the contrast right. This is achieved by including my <a href="http://lea.verou.me/2011/02/incrementable-length-values-in-text-fields/">Incrementable</a> library.</li>
</ul>
<p>Browser support is IE10 and modern versions of Firefox, Safari, Chrome, Opera. Basic support for IE9. No responsive version yet, sorry (but you can always <a href="https://github.com/LeaVerou/contrast-ratio">send pull requests</a>!)</p>
<p>Save the link: <a href="https://contrast-ratio.com/">contrast-ratio.com</a></p>
<p><em>Edit 2022: Link updated to reflect current one. Original link was <a href="http://leaverou.github.com/contrast-ratio">leaverou.github.com/contrast-ratio</a></em></p>
Lots of improvements coming to dabblet2012-11-07T00:00:00Zhttps://lea.verou.me/?p=1962<p>I posted about this in both the <a href="http://blog.webplatform.org/2012/11/live-examples-coming-soon/">WebPlatform.org blog</a> and <a href="http://blog.dabblet.com/post/35203393585/lots-of-improvements-in-the-pipeline">Dabblet’s blog</a>, but I thought it might be interesting to many readers of this blog as well:</p>
<blockquote>
<p>As many of you probably know, <a href="http://lea.verou.me/">I</a>’ve started working for <a href="http://www.w3.org/community/devrel/">W3C Developer Relations</a> since this August. Half of my time is devoted to <a href="http://www.webplatform.org/">WebPlatform.org</a>, a very promising project to document the web with the help of all major players, in a vendor-neutral way. Even before I joined <a href="http://w3.org/">W3C</a>, we discussed using a hosted, customized version of <a href="http://dabblet.com/">dabblet</a> in <a href="http://webplatform.org/">WebPlatform.org</a>, as a platform for live code examples. I recently started working towards making this happen.</p>
<p>A lot of changes and improvements need to be made to achieve this, but the good news is, most of these will be pushed to <a href="http://dabblet.com/">dabblet.com</a> as well! In a nutshell, this is what I’m currently working on:</p>
<ul>
<li>Adding JavaScript support — This will be a challenge UX-wise, as it shouldn’t run on every keystroke, like the HTML and CSS, but it should run on startup and it should be straight-forward how to get it to run. Perhaps it will also run after a significant pause in typing.</li>
<li>Dabblets that are not stored in Github, but get their data through POST requests.</li>
<li>Improving cross-browser support</li>
<li>Strengthening security</li>
<li>Integrating <a href="http://prismjs.com/">Prism</a>. Dabblet’s syntax highlighter might have been Prism’s precursor, but currently Prism has solved many of its bugs, and these fixes need to be pushed to dabblet at some point.</li>
<li>General bug fixing</li>
</ul>
<p>These will probably be gradually rolled out in <a href="http://dabblet.com/">dabblet.com</a> and tested by the community, before we integrate dabblet into <a href="http://webplatform.org/">WebPlatform.org</a>. If a new feature is significant enough, there will be a new blog post about it here, but don’t expect blog posts about bugfixes. I’m really excited to see dabblet flourish, and I believe you will be too, once these updates are out!</p>
</blockquote>
CSS Animations with only one keyframe2012-12-12T00:00:00Zhttps://lea.verou.me/?p=1975<p>This is a very quick tip, about a pet peeve of mine in almost every CSS animation I see. As you may know, I’m a sucker for reducing the amount of code (as long as it remains human readable of course). I demonstrated a very similar example in my “CSS in the 4th dimension” talk, but I recently realized I never blogged about it (or seen anyone else do so).</p>
<p>Lets assume you have a simple animation of a pounding heart, like so:</p>
<pre><code>@keyframes pound {
from { transform: none; }
50% { transform: scale(1.4); }
to { transform: none; }
}
.heart {
/* ... */
animation: pound .5s infinite;
}
</code></pre>
<iframe src="http://dabblet.com/gist/4268782/a7d1b285a6af0a0183f94079ab310217c1076275" style="width:100%; height:500px"></iframe>
<p>You can see the problem already: the shrunk heart state is repeated twice in the keyframes (<code>from</code> and <code>to</code>). You probably know you can combine them into one rule, like so:</p>
<pre><code>@keyframes pound {
from, to { transform: none; }
50% { transform: scale(1.4); }
}
</code></pre>
<iframe src="http://dabblet.com/gist/4268782/b8beab24426225097410b9b159d27a5cf7e4e3fd" style="width:100%; height:500px"></iframe>
<p>What many don’t know, is that you don’t need these two keyframes at all, since they basically replicate the same state as the one in the <code>.heart</code> rule. To quote the <a href="http://www.w3.org/TR/css3-animations/#keyframes">CSS Animations spec</a>:</p>
<blockquote>
<p>If a 0% or “from” keyframe is not specified, then the user agent constructs a 0% keyframe using the computed values of the properties being animated. If a 100% or “to” keyframe is not specified, then the user agent constructs a 100% keyframe using the computed values of the properties being animated.</p>
</blockquote>
<p>Therefore, the code could actually be as simple as:</p>
<pre><code>@keyframes pound {
50% { transform: scale(1.4); }
}
</code></pre>
<iframe src="http://dabblet.com/gist/4268782/b7849dbbd47761cf352fe7e0740c4bc227824f61" style="width:100%; height:500px"></iframe>
<p>This trick is very useful for providing fallbacks that are the same as the first or last keyframe, without having to repeat them in the <code>@keyframes</code> rule. Of course it doesn’t only apply to animations where you only have one keyframe beyond <code>from</code> and/or <code>to</code>. You can omit the <code>from</code> and <code>to</code> keyframes in every animation, when you want them to be the same as the styles that are applied to the element anyway.</p>
<p>Of course, to make this particular animation appear more natural, it would be much more wise to do something like this, still with only one keyframe (the <code>from</code> state is dynamically generated by the browser):</p>
<pre><code>@keyframes pound {
to { transform: scale(1.4); }
}
.heart {
/* ... */
animation: pound .25s infinite alternate;
}
</code></pre>
<iframe src="http://dabblet.com/gist/4268782" style="width:100%; height:500px"></iframe>
<p>which just reverses every even iteration, instead of trying to have both states (shrinking and growing) in the animation. The reason this looks more natural is that <code>animation-direction: alternate;</code> (which is what the <code>alternate</code> keyword does in the animation shorthand) also reverses the timing (easing) function for the reversed iterations. ;)</p>
One year of pastries2012-12-30T00:00:00Zhttps://lea.verou.me/?p=1995<p>Last September, I was approached by <a href="http://bitspushedaround.com/">Alex Duloz</a>, who invited me to take part in his ambitious new venture, <a href="http://the-pastry-box-project.net/">The Pastry Box Project</a>. Its goal was to gather 30 people (“bakers”) every year who are influential in their field and ask them to share twelve thoughts — one per month. For 2012, that field would be the Web. I was honored by the invitation and accepted without a second thought (no pun intended). The project was quite successful and recently we all (<a href="https://twitter.com/mollydotcom/status/281108923170308096">almost</a>) agreed for The Pastry Box Project to become a book, whose profits will be donated to charity.</p>
<p>The initial goal of the project was to gather thoughts somehow related to the bakers’ work. Although many stuck to that topic, for many others it quickly drifted away from that, with them often sending thoughts that were general musings about their lives or life in general. For me …well lets just say I was never good at sticking to the topic at hand. ;)</p>
<p>The Pastry Box showed me that I want a personal blog so <a href="http://pensieve.verou.me/">I made one today</a>. I will still publish personal stuff here, as long as it’s even remotely web-related, so not much will change. However, my interests range to more than the Web, so I will now have another medium to express myself in. :)</p>
<p>Since 2012 is now over, I decided to gather all my “pastries” and publish them in two blog posts: I will post the more techy/professional ones below and the more general/personal ones in <a href="http://pensieve.verou.me/post/39242403969/one-year-of-pastries">my personal blog</a>. Since most of them were somewhere in the middle, it wasn’t easy to pick which ones to publish where. I figured the best solution is to allow for some overlap and publish most of them in both blogs.</p>
<h2 id="january" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#january">January</a></h2>
<p>Often people ask me how I come up with the new ideas I publish. I think my main “differentiator” is that I try not to be restricted by my knowledge about what’s possible and what is not. I first think about what I want to make (for example “I want to do a rating widget with pure CSS”) and then I investigate how it could be done. And I don’t give up easily. Sometimes it even takes months having the question in the back of my head before I come up with a solution.</p>
<p>People push the boundaries of what’s achievable with web technologies every day. Do you want to be one of them, or do you want to be stuck repeating what’s been done over and over again until you get sick of it? Don’t be afraid to try new things. If a voice inside you screams “That isn’t possible!”, ignore it. In most cases, this voice is wrong.</p>
<h2 id="february" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#february">February</a></h2>
<p>You may catch more flies with honey than with vinegar, but you catch even more with a little audacity. Being polite is a good rule of thumb, but like everything, it also needs moderation. Don’t say “share my content pleeeeeeaaaase”. It makes people think your content isn’t worthy of sharing if you have to grovel. In a long email, don’t write a paragraph apologizing for its length (true story!). Being overly polite when meeting someone, categorizes yourself as inferior in the other person’s subconscious. Treat yourself with the respect and admiration you expect from other people. If you don’t think highly of yourself, nobody will. When meeting someone you admire, treat them as an equal and they’re more likely to do the same.</p>
<p>However, be careful not to cross the fine distinction between treating yourself with respect and being a cocky jackass. Treat others as equals, not as inferiors, otherwise your attitude will get you nowhere — and will piss everyone off along the way.</p>
<h2 id="march" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#march">March</a></h2>
<p>You can get quite far by putting cool stuff out there and expecting everything to come to you. Yes, you will eventually get job offers, conference invitations and various distinctions. However, sometimes, just asking will get you what you want much faster.</p>
<p>I used to avoid asking like the plague, and thought that if my work is good enough, what I want will naturally come to me. Which makes sense, to a certain extent: When someone keeps asking for stuff all the time, you can’t help but think that they merely see you as means to an end.</p>
<p>However, when you really want something, it never hurts to approach it yourself. Lately, I’ve been experiencing how much easier this makes things, and I’d strongly recommend you try it too. Turns out that quite often you don’t have what you want not because you aren’t good enough, but because the parties involved have no idea you’re interested.</p>
<h2 id="april" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#april">April</a></h2>
<p>The best argument against conventional wisdom is the fate of everyone following it. If you aspire beyond mediocrity, conventional wisdom is recipe for failure. Think out of the box. What can you do to achieve your goals, that others are not already doing? The least popular paths are the most successful. The trick isn’t doing better than the others, it’s minimizing the number of “others”. Find unexplored territory and make it yours. It’s much easier than trying to claim your stake on someone else’s land.</p>
<h2 id="may" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#may">May</a></h2>
<p>I never make long-term plans. Life is an unpredictable adventure. Concrete plans restrict this amazing journey. Stressing over a series of mental checkboxes you need to check until a certain date shifts your focus away from making awesomeness. I have long-term dreams instead, and they are all the compass I need. They give me the drive to constantly strive to improve, while still allowing room for surprises. I learned to trust chaos, and so far, I was never disappointed.</p>
<h2 id="june" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#june">June</a></h2>
<p>Contrary to popular belief, the defining characteristic of a good professional, in any discipline, is not the ability to blurt out good ideas off the top of their head. It’s perseverance and not being easily satisfied. Where the others would stop, they keep going. For example, when writing CSS, they won’t stop after they’ve achieved a certain style. They will also try to make it more flexible, more maintainable, simpler. Next week, try this: When you’re about to give up and proclaim that something is “done”, try to spend five more minutes on the task, thinking how you can improve it further, how to make it more elegant. I think it will help you be much more satisfied and proud of your work.</p>
<h2 id="july" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#july">July</a></h2>
<p>Before you start complaining about what you don’t like in CSS, HTML or JavaScript, ask yourself: How would I do it better? Sometimes, the things that bother us are just unavoidably subpar solutions to very hard problems. It sounds obvious, but many people I’ve spoken with get a completely new perspective when they ask themselves this question. Also, there are many other factors affecting design choices, beyond syntactical elegance and ease of understanding. For example, making implementations easier, maintaining backwards compatibility or matching what browsers already do. Sometimes that “obvious better solution” is just not possible in practice.</p>
<h2 id="august" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#august">August</a></h2>
<p>We all teach from time to time, whether it’s explaining something to a colleague, writing a blog post about the cool CSS technique we discovered, or giving a technical talk. If you are serious about becoming better at it, I’d strongly recommend reading up on psychology and neuroscience. If you don’t have the time to, here’s one fact that I’ve found most useful: Humans have incredibly impressive pattern recognition skills. We use them in pretty much everything we do, from learning our native language as kids, to escaping predators in the wild.</p>
<p>How does that help you teach more effectively? In one word: Examples, examples, examples. No matter how good you are at explaining the rules, nothing beats a few good examples of their application in practice. Our abstract thinking is not nearly as good as our pattern recognition skills.</p>
<p>However, don’t be fooled into thinking that theory is useless. Often, multiple explanations fit a given example. The theory helps us pick the one that fits, which might not be the one we initially recognized.</p>
<p>I’ve found that this principle applies to pretty much everything I’ve taught or have been taught, from mathematics to natural and programming languages. You can forget the theory, but you should never forget the examples.</p>
<h2 id="september" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#september">September</a></h2>
<p>People often think it’s hard to change my mind, that I’m too fixated on my own opinions. The reason I give this impression is that I will fiercely defend them. However, I will only do so until I see compelling arguments for the other side. I always try to keep an open mind to being wrong, and it has only made me better.</p>
<p>In the past few months I’ve been witnessing myself slowly change my views regarding yet another major life issue: The place I want to live in. Moving to the US has been a life goal for me ever since I first visited, almost fifteen years ago. However, as I spend more time there and get closer to moving, I’ve started noticing things that I don’t like so much. I’ve tried to ignore them, but they keep being there, giving me the finger like dead pixels on a brand new screen. I might go forward with it anyway, or I might pick another country, but this is yet another experience that has taught me to avoid being dogmatic.</p>
<p>We are all, and should be, subject to change. Whoever insists in their rigid convictions reminds me of software whose bugs never get fixed. You are the only maintainer of that software. Be vigilant enough to discover and fix your own bugs. Be open-minded enough to listen to other people’s bug reports about it. Most people forget to do this after a certain age. They become so arrogant that they think they don’t have any more bugs to fix, or so insecure that they believe they can’t fix any more. That’s the turning point where the years that pass by start to become “aging”, instead of “growing up”. Aging doesn’t have to do with how long you’re on this planet, it has to do with giving up on yourself. To stop being subject to change is to start being stagnant.</p>
<h2 id="october" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2012/12/one-year-of-pastries/#october">October</a></h2>
<p>These days, we almost all unequivocally embrace graceful degradation and progressive enhancement. It’s the extent that people disagree on, since everyone has a different definition of what is “graceful” and what is “enhancement”. Is a solid color an acceptable fallback for a pattern? What if your lightbox has no overlay? What if your stripes become a solid color? What if your transitions are not there? What if your code has no syntax highlighting? That’s the true challenge: How different can they look? Is it sufficient if the content is accessible in IE8 or does it also have to be pretty? How pretty? Those are the questions you need to agree on with your team to ensure you’re all on the same page. An agreement on the basic premise that websites don’t have to look the same in every browser is far from enough. Graceful degradation is not black & white, it’s a spectrum. You need to find where you lie on that spectrum and where your colleagues lie on it too, otherwise expect a lot of tension every time decisions need to be made.</p>
W3Conf in San Francisco, February 21-222013-01-30T00:00:00Zhttps://lea.verou.me/?p=2006<p>You might have heard about <a href="http://w3.org/conf">W3Conf</a>, <a href="http://w3.org/">W3C</a>’s conference for web designers and developers. This year, I have the pleasure of not only speaking there but also organizing it, along with <a href="http://twitter.com/shepazu">Doug Schepers</a> and designing the <a href="http://w3.org/conf">website</a> for it.</p>
<p><a href="http://w3.org/conf"><img src="https://lea.verou.me/2013/01/w3conf-in-san-francisco-february-21-22/images/Screen-Shot-2013-01-30-at-02.43.26--1024x640.png" alt="" title="Website screenshot" /></a></p>
<p>Alongside with yours truly, it features an excellent lineup of amazing speakers like <a href="http://twitter.com/meyerweb">Eric Meyer</a>, <a href="https://twitter.com/Fyrd">Alexis Deveria</a> of <a href="http://caniuse.com/">caniuse.com</a> fame, <a href="https://twitter.com/necolas">Nicolas Gallagher</a> and many others. You can use coupon code <strong>VEROU</strong> to get <strong>$100 off</strong> the already affordable Early Bird price of <strong>$300</strong>. But hurry up, cause Early Bird prices are only valid <strong>until January 31st</strong>!</p>
<p>Hope to see you there!</p>
iOS 6 switch style checkboxes with pure CSS2013-03-15T00:00:00Zhttps://lea.verou.me/?p=2027<p>I recently found myself looking at the Tools switch in Espresso:</p>
<p><img src="https://lea.verou.me/2013/03/ios-6-switch-style-checkboxes-with-pure-css/images/Screen-Shot-2013-03-15-at-15.32.33-.png" alt="" title="Screen Shot 2013-03-15 at 15.32.33" /></p>
<p>Not because I was going to use it (I rarely do), but because I started wondering what would be the best way to replicate this effect in CSS. I set on to create something that adhered to the following rules:</p>
<ol>
<li>It should be keyboard accessible</li>
<li>It should work in as many browsers as possible and degrade gracefully to a plain checkbox in the rest</li>
<li>It shouldn’t depend on pseudo-elements in replaced elements (such as checkboxes), since that’s non-standard so not very dependable</li>
<li>It shouldn’t require any extra HTML elements</li>
<li>It shouldn’t use JS, unless perhaps to generate HTML that could be written by hand if the author wishes to do so.</li>
</ol>
<p>Why you may ask? Some of them are good practices in general, and the rest make it easier to reuse the component (and they made it more challenging too!).</p>
<p>The best idea I came up with was to use a radial gradient for the knob and animate its background-position. All that on a checkbox. After a lot of tweaking, I settled on something that looked decent (although not as good as the Espresso one) in the browser I was using (Chrome) and went ahead to test it in others. The result was disappointing: I had forgotten that not all browsers allow that kind of customization on checkboxes. And who can blame them? This is what happens when you’re wandering in Undefined Behavior Land. They are not violating any spec, because there is no spec mandating or forbidding checkboxes from being stylable with CSS and to what extent, so every browser does its thing there.</p>
<p>Here you can see my failed attempt, which only works as intended in Chrome:</p>
<iframe src="http://dabblet.com/gist/5078981/457e62ee672ba69fe6ce5a3f6c173528366a2203" width="100%" height="200"></iframe>
<p>I realized I had to lift one of the restrictions if I wanted to solve this, so I picked the 4th (no extra HTML elements), as it was the least important one. I could have done it as a pseudoelements on <code><label></code>s, but I decided to use a <code><div></code> instead, for maximum flexibility. The <code><div></code> is added through script in the Dabblet below, but it could be added by hand instead.</p>
<iframe src="http://dabblet.com/gist/5078981" width="100%" height="500"></iframe>
<p>To get around the limitation of pseudo-elements not being animatable in current and older versions of WebKit, I animate the padding of the <code><div></code> instead.</p>
<p>And then I thought, why not make iOS-style switches? Even more challenging! I turned on my iPhone and tried to replicate the look. Adding the ON/OFF text was very painful, as it needs to both animate and be styled differently for “ON” and “OFF”. Eventually, I ended up doing it with <code>text-indent</code> in such a way that it depends on the knob’s position, so that when the knob animates, the text moves too.</p>
<p>Another challenge with this was the different backgrounds. Changing the background color upon <code>:checked</code> was not enough, since it needs to slide as well, not just abruptly change or fade in. I ended up doing it with a gradient and animating its background-position. Naturally, this makes it not look as good in IE9.</p>
<p>So, without further ado, here is the final result:</p>
<iframe src="http://dabblet.com/gist/5166717" width="100%" height="800"></iframe>
<p>Yes, I know there are other efforts on the web to replicate this effect with pure CSS, but none of them seems to come as close to the original, without images and with such minimal HTML.</p>
<p>Why bother, you may ask? Well, it was a fun pastime during SXSW breaks or sessions that turned out to be less interesting than expected or in the plane on the way home. Besides, I think that it could be useful in some cases, perhaps if the styling is tweaked to not resemble iOS too obviously or maybe in iOS app mockups or something.</p>
<p>Enjoy!</p>
<p><em>Credits to <a href="http://www.thecssninja.com/css/custom-inputs-using-css">Ryan Seddon</a> for paving the way for custom form elements through CSS, a couple years ago</em></p>
Use MathML today, with CSS fallback!2013-03-21T00:00:00Zhttps://lea.verou.me/?p=2041<p>These days, I’m working on the slides for <a href="http://lea.verou.me/speaking">my next talk</a>, “The humble border-radius”. It will be about how much work is put into CSS features that superficially look as simple as border-radius, as well as what advances are in store for it in <a href="http://dev.w3.org/csswg/css4-background">CSS Backgrounds & Borders 4</a> (of which I’m an editor). It will be fantastic and you should come, but this post is not about my talk.</p>
<p>As you may know, my slides <a href="http://github.com/LeaVerou/CSSS">are made with HTML, CSS & JavaScript</a>. At some point, I wanted to insert an equation to show how border-top-left-radius (as an example) shrinks proportionally when the sum of radii on the top side exceeds the width of the element. I don’t like LaTeX because it produces bitmap images that don’t scale and is inaccessible. The obvious open standard to use was <a href="http://www.w3.org/TR/MathML/">MathML</a>, and it can even be directly embedded in HTML5 without all the XML cruft, just like SVG. I had never written MathML before, but after a bit of reading and poking around existing samples, I managed to write the following MathML code:</p>
<pre><code class="language-markup"><math display="block">
<mrow>
<msub>
<mi>r&prime;</mi>
<mi>top-left</mi>
</msub>
<mo>=</mo>
<mi>min</mi>
<mo>(</mo>
<msub>
<mi>r</mi>
<mrow>
<mi>top-left</mi>
</mrow>
</msub>
<mo>,</mo>
<mi>width</mi>
<mo>&times;</mo>
<mfrac>
<mrow>
<msub>
<mi>r</mi>
<mi>top-left</mi>
</msub>
</mrow>
<mrow>
<msub>
<mi>r</mi>
<mi>top-left</mi>
</msub>
<mo>+</mo>
<msub>
<mi>r</mi>
<mi>top-right</mi>
</msub>
</mrow>
</mfrac>
<mo>)</mo>
</mrow>
</math>
</code></pre>
<p>I was very proud of myself. My first MathML equation! It’s actually pretty simple when you get the hang of it: <code><mi></code> is for identifiers, <code><mo></code> for operators and those are used everywhere. For more complex stuff, there’s <code><mfrac></code> for fractions (along with <code><mrow></code> to denote the rows), <code><msqrt></code> for square roots and so on.</p>
<p>It looked very nice on Firefox, especially after I applied Cambria Math to it instead of the default Times-ish font:</p>
<p><a href="https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/images/mathml-firefox.png"><img src="https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/images/mathml-firefox.png" alt="MathML native support in Firefox" /></a> <a href="https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/images/mathml-chrome-withcss.png"></a></p>
<p>However, I soon realized that as awesome as MathML might be, <a href="http://docs.webplatform.org/wiki/mathml#Compatibility">not not all browsers had seen the light</a>. IE10 and Chrome are the most notable offenders. It looked like an unreadable mess in Chrome:</p>
<p><a href="https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/images/mathml-chrome-nocss.png"><img src="https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/images/mathml-chrome-nocss.png" alt="MathML in Chrome, with no CSS fallback" /></a></p>
<p>There are libraries to make it work cross-browser, the most popular of which is <a href="http://www.mathjax.org/">MathJax</a>. However, this was pretty big for my needs, I just wanted <strong>one</strong> simple equation in <strong>one</strong> goddamn slide. It would be like using a chainsaw to cut a slice of bread!</p>
<p>The solution I decided to go with was to use <a href="http://modernizr.com/">Modernizr</a> to detect MathML support, since apparently <a href="https://github.com/Modernizr/Modernizr/blob/master/feature-detects/mathml.js">it’s not simple at all</a>. Then, I used the <code>.no-mathml</code> class in conjunction with selectors that target the MathML elements, to mimic proper styling with simple CSS. It’s not a complete CSS library by any means, I just covered what I needed for that particular equation and tried to write it in a generic way, so that if I need it in future equations, I only have to <em>add</em> rules. Here’s a screenshot of the result in Chrome:</p>
<p><a href="https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/images/mathml-chrome-withcss.png"><img src="https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/images/mathml-chrome-withcss.png" alt="MathML in Chrome with CSS fallback" /></a></p>
<p>It doesn’t look as good as Firefox, but it’s decent. You can see the CSS rules I used in the following Dabblet:</p>
<iframe src="http://dabblet.com/gist/5214646" height="500" width="100%"></iframe>
<p>Obviously it’s not a complete MathML-to-CSS library, if one is even possible, but it works well for my use case. If I have to use more MathML features, I’d write more CSS rules. The intention of this post is not to provide a CSS framework to use as a MathML fallback, but to show you a solution you could adapt to your needs. Hope it helps!</p>
Preview corner-shape, before implementations!2013-03-24T00:00:00Zhttps://lea.verou.me/?p=2074<p>As an editor of the <a href="http://dev.w3.org/csswg/css4-background/">Backgrounds & Borders Level 4</a> spec, I am naturally a bit more interested in the cool features it will bring, once implementations start (it’s currently too early for that). One of the coolest features in it is <a href="http://dev.w3.org/csswg/css-backgrounds-4/#corner-shaping">corner-shape</a>. While in <a href="http://www.w3.org/TR/css3-background/">Backgrounds & Borders 3</a>, <a href="http://www.w3.org/TR/css3-background/#the-border-radius">border-radius</a> was only used for rounded (actually, elliptical) corners, with the help of corner-shape, it will be able to do so much more! Beveled corners, scoop-style corners (informally known as “negative border-radius”), even rectangular notches.</p>
<p>Unfortunately, until it’s implemented in browsers, it’s hard to play with it. Or, is it? I spent the weekend creating an app in which you can enter values for corner-shape, border-radius, width, and height, and see the result, simulated through SVG, as well as the fallback in browsers that don’t support border-corner-radius (which is currently all browsers).</p>
<p><a href="https://lea.verou.me/2013/03/preview-border-corner-shape-before-implementations/images/Screen-Shot-2013-03-24-at-17.45.38-.png"><img src="https://lea.verou.me/2013/03/preview-border-corner-shape-before-implementations/images/Screen-Shot-2013-03-24-at-17.45.38-.png" alt="corner-shape preview" /></a></p>
<p>Obviously, it’s not a full preview, since you can only play with a limited subset of CSS properties, but it should be good for seeing the kinds of shapes that will be possible.You could also copy the generated SVG from the Developer tools of your browser, and use it as a background in any website!</p>
<p>Use it here: <a href="https://projects.verou.me/corner-shape">corner-shape preview</a></p>
<p>Tested to work in at least Chrome, IE9, Firefox, Safari and theoretically, should work in any SVG-enabled browser.</p>
<p>Enjoy! Hope you like it.</p>
<p><strong>Important:</strong> Please note that corner-shape is still at a very early stage and might completely change before implementations. You can also help to make it better: Play with it and comment on what you think about its naming and functionality!</p>
Easily center text vertically, with SVG!2013-03-24T00:00:00Zhttps://lea.verou.me/?p=2065<p>These days, we have a number of different ways to vertically align text in a container of variable dimensions:</p>
<ul>
<li>Table display modes</li>
<li>Flexbox</li>
<li>inline-block hacks</li>
<li>Wrapping the text in an extra element and absolutely positioning it</li>
<li>…and probably many others I’m forgetting</li>
</ul>
<p>However, often comes a time when neither is suitable, so here I am, adding yet another option to the list. Of course, it comes with its own set of drawbacks, but there are cases where it might be better than the existing solutions.</p>
<p>It all started when I discovered the <a href="http://www.w3.org/TR/SVG/text.html#TextAnchorProperty"><code>text-anchor</code></a> SVG property. It determines where the <a href="http://www.w3.org/TR/SVG/text.html#TextElementXAttribute">x</a> and <a href="http://www.w3.org/TR/SVG/text.html#TextElementYAttribute">y</a> attributes on <a href="http://www.w3.org/TR/SVG/text.html#TextElement"><code><text></code></a> elements refer to. The magic starts when you set it to “middle”, then the <a href="http://www.w3.org/TR/SVG/text.html#TextElementXAttribute">x</a> and <a href="http://www.w3.org/TR/SVG/text.html#TextElementYAttribute">y</a> attributes refer to the center of the text. So, if you set those to 50%, they refer to the center of the SVG graphic itself, and if you set the SVG width and height to 100%, the text basically sits in the center of the <code><svg></code>’s container, which could be any HTML element!</p>
<p>One issue was that this centered the baseline of the text, so I tried to find a way to shift the baseline appropriately. Setting <a href="http://www.w3.org/TR/SVG/text.html#DominantBaselineProperty"><code>dominant-baseline</code></a><code>: middle;</code> on the <a href="http://www.w3.org/TR/SVG/text.html#TextElement">`<text>``](http://www.w3.org/TR/SVG/text.html#TextElement) element seemed to fix it, but it looks like IE doesn’t support that. I ended up adding [`dy`](http://www.w3.org/TR/SVG/text.html#TextElementDYAttribute)=“.3em” to the [`<text>`</text></text></a> element, which fixes it but might need to be adjusted if you change the line-height.</p>
<p>In addition, this method has the following drawbacks I can think of:</p>
<ul>
<li>Extra markup (namely 2 elements: <code><svg></code> and <a href="http://www.w3.org/TR/SVG/text.html#TextElement"><code><text></code></a>)</li>
<li>If the text is more than one line, it won’t automatically wrap, you have to do it manually.</li>
<li>Some new-ish CSS text properties may not be applied. For example, text-shadow is applied in Chrome but not in Firefox, since technically, it’s still not a part of the SVG spec.</li>
<li>You need to duplicate the text color as a fill property, since SVG does not understand the color CSS property. No need to duplicate anything, just use <code>fill: currentColor;</code> (<a href="http://lea.verou.me/2013/03/easily-center-text-vertically-with-svg/#comment-841846526">thanks GreLI!</a>)</li>
</ul>
<p>However, it has a few advantages too:</p>
<ul>
<li>You don’t need to change anything on the parent HTML element</li>
<li>Degrades gracefully in non-SVG browsers</li>
<li>Should be perfectly accessible and won’t break SEO</li>
<li>Works perfectly in IE9, unlike Flexbox</li>
<li>You can include any kind of SVG styling on the text. For example, strokes!</li>
</ul>
<p>You can see and play with the result in the dabblet below:</p>
<iframe src="http://dabblet.com/gist/5229803" height="500" width="100%"></iframe>
<p>Verified to work in at least Chrome, Firefox, IE9+. Hope it’s useful, even though it won’t be a good fit in every single use case.</p>
border-corner-shape is in danger, and you can help!2013-03-28T00:00:00Zhttps://lea.verou.me/?p=2091<p>Remember <a href="http://lea.verou.me/2013/03/preview-border-corner-shape-before-implementations/">my previous post about an app I made to preview border-corner-shape</a>? It stirred a lot of discussion in the CSS WG, and <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0498.html">David Baron posted this</a>:</p>
<blockquote>
<p><a href="http://dev.w3.org/csswg/css4-background/#border-corner-shape">http://dev.w3.org/csswg/css4-background/#border-corner-shape</a> appears to me to be an example of a feature that’s addressing a problem that we don’t have – or at least that we don’t have enough to be worth adding such a feature. I think it should be removed.</p>
<p>In particular, if there were demand for the bevel | curve | notch values, we’d be seeing authors using the equivalents of such values on significant numbers of Web sites. So before agreeing to accept this new feature, I’d like to see examples of Web sites that are doing what these values would do. Not something similar to what these values would do, but exactly what these values would do, or at least close enough that the author wouldn’t care about the difference.</p>
</blockquote>
<p>You can read the whole discussion in the thread I linked to, above.</p>
<p>I might be wrong, but I believe <a href="http://dev.w3.org/csswg/css4-background/#border-corner-shape">border-corner-shape</a> would greatly simplify many common effects, especially its “bevel” value, which can even create triangles and other polygons, that we go to great lengths to make with CSS today, and it would degrade much more nicely than border-image or backgrounds. I think it was one of <a href="http://fantasai.inkedblade.net/">fantasai</a>’s many great ideas and I’m glad she added it to <a href="http://dev.w3.org/csswg/css4-background/">the Editor’s Draft of Backgrounds & Borders 4</a>.</p>
<p>I posted a <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0505.html">list of tutorials and questions from web designers & developers</a>, to illustrate that these effects are needed. However, David <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0507.html">argued that “Questions from authors don’t give you enough information to be sure that the feature being added is sufficient for the author’s needs</a><a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0507.html">”</a>. He did have a point, so with some help from the community, <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0653.html">I posted a few links to websites using such effects, and use cases</a>. <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0519.html">Nicole Sullivan</a>, <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0538.html">Liam Quin</a>, <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0648.html">fantasai</a> and <a href="http://lists.w3.org/Archives/Public/www-style/2013Mar/0666.html">Lev Solntsev</a> posted a couple more.</p>
<p>However, the more real examples we have, the more likely it is to retain the feature in some form. This is where you come in: If you think border-corner-shape has merit, provide use cases, either by links to websites whose design elements it would simplify, screenshots of websites or descriptions of cases where you needed such a thing (in that order of preference). You can either post to the thread directly, or comment here and I’ll post them to the list in batches.</p>
<p>If you think it has merit but it could be improved, feel free to post about that as well. If you don’t think it’s a good idea, any alternatives you can think of are welcome as well. Or, if you don’t think it’s useful, say that too (but make sure you first fully understand what it can do). If you’re not sure how it can be used, play around with <a href="https://projects.verou.me/corner-shape/">the demo app I made</a> and be creative!</p>
<p>Below are a few examples of shapes:</p>
<p><a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-1.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-1.png" alt="bevel-1" /></a><a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/scoop-1.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/scoop-1.png" alt="scoop-1" /></a><a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/notch-1.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/notch-1.png" alt="notch-1" /></a> <a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/notch-2.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/notch-2.png" alt="notch-2" /></a> <a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/scoop-2.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/scoop-2.png" alt="scoop-2" /></a> <a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-4.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-4.png" alt="bevel-4" /></a> <a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-3.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-3.png" alt="bevel-3" /></a> <a href="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-2.png"><img src="https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/images/bevel-2.png" alt="bevel-2" /></a></p>
<p>I wanted to demo triangles and trapezoids as well, but it seems there’s a bug in my app, so I’ll have to debug it first :( If we allow border-corner-shape to have different values for all four corners, even more possibilites open (e.g. arrows).</p>
<p>Spend a few minutes to help the CSS WG help you. Thanks!</p>
Can we get rid of gradient prefixes?2013-04-07T00:00:00Zhttps://lea.verou.me/?p=2137<p>I recently realized that unprefixed gradients finally propagated to stable Chrome, and after <a href="https://twitter.com/LeaVerou/status/320365600998305792">tweeting about it</a>, I decided to do some research on which browsers support unprefixed gradients, and what percentage of users needs them.</p>
<p>Currently, unprefixed gradients are supported in:</p>
<ul>
<li>Chrome 26+</li>
<li>Firefox 16+</li>
<li>Opera 12.10+</li>
<li>IE10+</li>
</ul>
<p>Lets have a look at which prefixes we actually need to use for gradients today.</p>
<h3 id="-ms-" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/04/can-we-get-rid-of-gradient-prefixes/#-ms-">-ms-</a></h3>
<p>There was <strong>never</strong> a stable release of IE that supported -ms- prefixed gradients, those were only in preview versions (stable IE10 supports both prefixed and unprefixed gradients). So, -ms- is most definitely not required.</p>
<h3 id="-moz-" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/04/can-we-get-rid-of-gradient-prefixes/#-moz-">-moz-</a></h3>
<p>Firefox versions >= 3.6 and < 16 account for <strong>4%</strong> of the global user base*. This might or might not be significant, depending on how good the fallback is that these users will see. If the gradient only adds a subtle shadow or something like that, I’d say ditch -moz-. If it’s more crucial to the design & branding, it might be wise to still keep it. More tech-focused websites probably have a much lower percentage than 4%, so it might be a good idea to drop it there completely.</p>
<h3 id="-o-" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/04/can-we-get-rid-of-gradient-prefixes/#-o-">-o-</a></h3>
<p>Opera unprefixed gradients in 12.10. Opera Mini never supported them. Opera versions < 12.10 currently account to a total of <strong>0.25%</strong> of the global user base*. I’d say it’s safe to ditch -o- in gradients in most cases.</p>
<h3 id="-webkit-" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/04/can-we-get-rid-of-gradient-prefixes/#-webkit-">-webkit-</a></h3>
<p>Chrome only very recently unprefixed gradients and Safari is a long way from doing so. Not to mention all the mobile browsers using WebKit. Unfortunately, we can’t ditch -webkit- in CSS gradients just yet.</p>
<h3 id="my-opinion" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/04/can-we-get-rid-of-gradient-prefixes/#my-opinion">My opinion</a></h3>
<p>Don’t use -ms- prefixed gradients, there’s absolutely zero point in doing so. Include -moz- for the less subtle gradients. No significant need for -o- gradients. -webkit- is still needed and probably will be at least until the end of 2013. Or, of course, just use <a href="https://projects.verou.me/prefixfree/">-prefix-free</a> and don’t bother. :P</p>
<p>Keep in mind that your stats might differ from global stats, so which prefixes you need to include might differ on a case by case basis. <strong>The purpose of this post is to alert you that maybe you don’t need all these prefixes, not to prescriptively tell you which ones to keep.</strong> Except -ms-, please don’t use that. There’s absolutely zero reason whatsoever.</p>
<p><strong>Last but not least, no matter which prefixes you include, always have a good solid color fallback!</strong></p>
<p></p>
<p>* Global market share statistics from <a href="http://gs.statcounter.com/#browser_version-ww-monthly-201301-201303">StatCounter</a>, for a 3 month period of January 2013 - March 2013. The graph on the website only displays the most popular browser versions, but downloading the CSV file gives you all of them.</p>
Meet dpi.lv: More than you probably wanted to know about screen DPI2013-06-10T00:00:00Zhttps://lea.verou.me/?p=2156<p><a href="https://lea.verou.me/2013/06/meet-dpi-lv-more-than-you-probably-wanted-to-know-about-screen-dpi/images/Screen-Shot-2013-06-10-at-13.41.39-.png">
<img src="https://lea.verou.me/2013/06/meet-dpi-lv-more-than-you-probably-wanted-to-know-about-screen-dpi/images/Screen-Shot-2013-06-10-at-13.41.39--300x221.png" alt="Screen Shot 2013-06-10 at 13.41.39" /></a></p>
<p>Yesterday (Sunday) I was on a 9.5 hour flight from Canada with no inflight entertainment (well, thanks Air Canada), so I did what every bored human being would do instead of watching movies: I decided to code an app! And out of the infinite set of possible apps somebody can make, I decided to make an app to calculate screen DPI/PPI.</p>
<p>You might be wondering if I’m still (?) sane, but you might be surprised to hear I found myself calculating screen PPIs quite frequently and wanted to save myself the hassle of doing the math every time. I’m a curious person and I wanted to know, even about products I would never buy and even when it wasn’t listed in the tech specs. Yes, my hobbies are somewhat weird. :o</p>
<p>I first thought about doing such an app a while ago, but never found the time to code it. The last time I had thought about it was a few days ago at the SF Apple Store with a friend. We were looking at the 27" Apple Thunderbolt displays in awe and thought they must have huge pixel density. After a few calculations in the console (which ironically produced a result faster than the Apple guy my friend asked), it turned out it was only …102. “I need to code an app to make this sort of calculation easy! People are being misled by marketing!” I thought.</p>
<p>Fast forward to my flight. You didn’t expect my laptop battery to last for 9.5 hours, right? Yeah, MacBook Air batteries are good, but not *that* good. Of course it eventually died so I had to find other ways to pass my time (I ended up sleeping — or trying to). However, by the time it died, I had gone over the threshold of being able to give it up, so I spent the rest of the day finishing it, despite my obvious jetlag and sleepiness. I was in the zone — You don’t just go sleeping when you’re in the zone, right?</p>
<p>Besides the DPI/PPI calculator, I added a few other fun things too:</p>
<ul>
<li>A list of devices with pre-calculated data (stored in a separate JSON file, which makes it easy to update — *hint, hint*)</li>
<li>Wrote a few FAQ items about DPI/PPI.</li>
<li>Like many of my apps, it supports link sharing through URL hashes (for examples, check the <a href="http://dpi.lv/#screens">screens</a> section).</li>
<li>I even bought a proper domain for it (<a href="http://dpi.lv/">dpi.lv</a>) and drew <a href="http://dribbble.com/shots/1107403-dpi-love">a logo</a>! The logo took hours by itself. Not just to draw it, but to simplify Illustrator’s ugly, repetitive SVG output (which is still better than what most other tools spit out). Hand-simplifying SVG is a meditative experience that I thoroughly enjoy, to the bewilderment of everyone who read <a href="https://twitter.com/LeaVerou/status/343864607368163329">my tweet about it</a>. Just for the lulz, here’s the <a href="http://dpi.lv/img/logo-ai.svg">before</a> and the 66% smaller <a href="http://dpi.lv/img/logo.svg">after</a> (the small design tweaks were intentional)</li>
<li>The screen that displays the result resizes to reflect the aspect ratio of the resolution you’ve selected. It even animates to it, with CSS transitions! Oh, and it also uses FlexBox to center the text vertically.</li>
</ul>
<p><strong><a href="http://dpi.lv/">Enjoy</a>!</strong></p>
<p>Of course it’s open source (under an MIT license, as usual), and you can <a href="https://github.com/LeaVerou/dpi">fork it on Github</a>, as usual. The JS is a bit of a mess, but I’m too tired to refactor it now. Same goes for the lack of favicon and tagline. Oh well. I still like it. :)</p>
<p><strong>Important:</strong> If you are on a display with multiple dots per pixel (e.g. Retina), the resolution (pixel width × pixel height) it tries to guess will be incorrect, so you’ll have to actually input the right one. The default resolution in there is just a hint, it doesn’t mean it’s “broken” if it doesn’t guess right, they’re editable fields. That said, it would be nice to guess right in those cases too, and I will look into it.</p>
Leaving W3C2013-07-11T00:00:00Zhttps://lea.verou.me/?p=2211<p>About a year ago, <a href="http://lea.verou.me/2012/08/lea-at-w3-org/">I announced I was joining W3C</a> as a full-time staff member, to work on Developer Relations and education. Working at <a href="http://w3.org/">W3C</a> was a dream come true and I can’t say I was disappointed. Over the past year I’ve worked with some amazingly brilliant people, hopefully increased awareness for web standards in the developer community and helped materialize the vision behind <a href="http://webplatform.org/">WebPlatform.org</a>. It’s been a fun ride and working for a non-profit was very fulfilling. If somebody told me a year ago that I would decide to leave W3C on my own free will, I would’ve asked them what they were smoking. However, our future selves often surprise us and although it was the most difficult decision of my life, I recently decided to leave. July 31st will be my last day at W3C. I will attempt to describe the reasons below for anyone interested, but in no way does me leaving mean that I don’t deeply appreciate W3C or that I regretted joining. If I could go a year back, I would make the same choice.</p>
<h2 id="reason-%231%3A-i-want-to-focus-on-other-projects" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/07/leaving-w3c/#reason-%231%3A-i-want-to-focus-on-other-projects">Reason #1: I want to focus on other projects</a></h2>
<p>I didn’t have much time to work on my pet projects, as my job was consuming pretty much the entire me. <strong>This is absolutely not W3C’s fault, it’s mine</strong> and a pretty common side effect of working from home. Pull requests kept piling up on Github, I didn’t have many ideas for new side projects or time for research & to come up with new techniques. I was able to work a bit on <a href="http://dabblet.com/">Dabblet</a> and a <a href="http://prismjs.com/plugins/wpd/">WPD Prism plugin</a>, as they were useful for <a href="http://webplatform.org/">WebPlatform.org</a>, but for the most part, I wanted to work more on open source projects, do more research, blog more etc. I also recently signed a book deal with <a href="http://oreilly.com/">O’Reilly</a> for a book on advanced CSS techniques (“CSS Secrets”, ETA Spring 2014) and I wanted to take some time off and write a great inaugural book, not just a decent one (and design it too!). I also kinda missed doing workshops or even client work, who knew!</p>
<p>Having more time will also mean I will be able to focus more on standards work, which is a huge passion of mine. I know it sounds odd to leave W3C to work more on …standards, but standards work was never a part of my job at W3C. If I wanted to devote time to actively participate in the CSS WG beyond the weekly telcon, or to <a href="http://dev.w3.org/csswg/css-backgrounds-4/">the specification I edit</a>, I would have to do it outside work hours. Obviously, I will still have to do it in my free time, but I recall having more of that when I was self-employed.</p>
<h2 id="reason-%232%3A-i-want-to-grow" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/07/leaving-w3c/#reason-%232%3A-i-want-to-grow">Reason #2: I want to grow</a></h2>
<p>I want to be in a job that’s a challenge, that helps me grow and become a better professional. While I appreciate <a href="http://webplatform.org/">WebPlatform.org</a>, I didn’t feel that doing front-end development & design on it made me particularly better at what I do, at least compared to other things I could have been doing in the past year. It could be a perfect opportunity to grow for someone else, but it wasn’t for me.</p>
<p>I did become a better public speaker over the past year, but I would likely be doing as many talks anyway. I got some valuable conference organizing experience from <a href="http://w3.org/conf">W3Conf</a>, which I thoroughly enjoyed working on, but that was only a small part of my work.</p>
<h2 id="reason-%233%3A-different-direction" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/07/leaving-w3c/#reason-%233%3A-different-direction">Reason #3: Different direction</a></h2>
<p>Had I stayed, my job description for the upcoming year would have a slightly different focus. Since W3C Developer Relations was a new activity, neither Doug (my manager) nor I were quite sure how we could make the biggest impact, so we were experimenting to some degree. A few months after I joined, <a href="http://webplatform.org/">WebPlatform.org</a> launched and we slowly concentrated our efforts on that. If I had stayed for another year, my job would have an even stronger <a href="http://webplatform.org/">WebPlatform.org</a> focus. Half of it would be front-end design & development and even writing documentation for a day per week. That meant I would have to cut down many parts of my job that I enjoyed and wanted to concentrate more on, such as public speaking and event planning, and though it includes some public-facing activities like gathering feedback from developers, I’d like to do even more of that. This was not a bad decision on W3C’s part — <a href="http://webplatform.org/">WebPlatform.org</a> needs somebody concentrating on those aspects of it. However, although I strongly believe in the vision behind the project, this was not what I would personally enjoy doing.</p>
<h2 id="thank-you%2C-w3c" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/07/leaving-w3c/#thank-you%2C-w3c">Thank you, W3C</a></h2>
<p>Even though I’m leaving W3C, it will always have a very special place in my heart. I met & worked with the most brilliant people I have ever met. Special mention to <a href="http://twitter.com/amyvdh">Amy</a>, who did not just prove to be an intelligent, interesting and kind person, but also a great friend in the past couple of weeks that I got to know her better. I got to visit <a href="http://mit.edu/">MIT</a> and work from there for a while, which was an incredible experience. I got to contribute to <a href="http://webplatform.org/">WebPlatform.org</a> which is a very ambitious and honorable project that I strongly believe in. I got to co-organize <a href="http://w3.org/conf">W3Conf</a>, which turned out to a successful and fun conference.</p>
<p>Me leaving is a personal decision that has less to do with W3C and more to do with what I want out of life. But I’m going to sorely miss the W3C Team, the culture, the technical discussions. It’s been a fun ride and I’m grateful for the chance and the trust W3C placed in me. In fact, I wouldn’t be surprised to find myself working for W3C again at some point in the future, in some way or in a different role.</p>
<p>But for now, here’s to the future! I’m thrilled.</p>
<h2 id="want-to-work-at-w3c%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/07/leaving-w3c/#want-to-work-at-w3c%3F">Want to work at W3C?</a></h2>
<p>As you can imagine, there is one more opening now. :) Are you a great designer with front-end development skills? Are you passionate about creating the best open web platform documentation on the Web? <strong><a href="http://www.w3.org/Consortium/Recruitment/#design-webplatform">Apply now!</a></strong> You will be able to work from wherever in the world you want, whatever hours in the day you want, you will have great autonomy and <a href="http://twitter.com/shepazu">a pretty cool boss</a>. Sweet, huh?</p>
What makes speakers happy2013-07-19T00:00:00Zhttps://lea.verou.me/?p=2233<p>I wish I could speak at <a href="http://cssconf.eu/">CSSConf.eu</a>, but unfortunately I had to decline the invitation, as it collided with a prior speaking engagement I had agreed on. I recently got another email from the organizers with an interesting question:</p>
<blockquote>
<p>We want to make this event as stress-free for our speakers as possible. Since you spoke at a bunch of events, can you share a tip or two about what will make a speakers’ life easier, and their stay more pleasant? Any typical mistakes we can avoid?</p>
</blockquote>
<p>I thought it was lovely that they care about their speakers enough to ask this, this already places them above average. I started writing a reply, but I soon realized this is information that could be useful for other conference organizers as well, so I decided to post it here instead. So, what makes speakers happy?</p>
<h2 id="the-baseline" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/07/what-makes-speakers-happy/#the-baseline">The baseline</a></h2>
<p>These are things every good conference is doing for their speakers, although they often miss one or two. They keep speakers happy, but they 're not out of the ordinary.</p>
<ul>
<li>Cover their flights, accommodation for the entire conference and ground transportation from/to the airport (with a car, not public transport!).</li>
<li>Do not expect them to go through the hassle of booking all those themselves and then sending you receipts. Offer it as an option, but book them yourself by default.</li>
<li>Do not book flights without confirming the itinerary and personal info with them first. Also, this sounds obvious, but it’s surprising how many conferences have made this mistake with me: <strong>Type their name correctly</strong> when booking flights!</li>
<li>If hotel WiFi is not free, make sure it’s covered and included in their reservation. Same goes for breakfast.</li>
<li>Offer a honorarium, at least to those who have to take time off work to speak at your event (e.g. freelancers). Even if your budget is small and can only give a tiny honorarium, it will at least cover their meals, cabs etc while there. If the honorarium is small and mainly intended to cover miscellaneous expenses of the trip, don’t ask them to submit an invoice to claim it.</li>
<li>Have a speakers dinner before the event, where they can meet and socialize with the other speakers. This is also good for the conference, as they get the chance to catch up with their speaker friends (there aren’t that many people on the conference circuit, so we often know each other and want to catch up) so they will talk more to the attendees during the conference. Make sure the speakers dinner does not overlap with the pre-party, if you have one.</li>
<li>Do a tech check before their talk to make sure everything is smooth. Have dongles for Mac laptops. Have clickers they could use. Use wireless lapel microphones. Have a reliable private wifi network for speakers to use if they need an internet connection for their talk.</li>
<li>Have breaks between talks so they have some margin of going overtime without impacting the schedule. If they are too stressed about going through their talk fast, it won’t be a very good talk.</li>
</ul>
<h2 id="going-the-extra-mile" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2013/07/what-makes-speakers-happy/#going-the-extra-mile">Going the extra mile</a></h2>
<p>These are all things one or more conferences have done for me, but they are not generally common so they are a positive surprise when they happen, not something expected.</p>
<ul>
<li>Book Business class flights, especially for longer flights where passengers are expected to sleep. It’s so much more comfortable to sleep in a seat that fully reclines! I was incredibly grateful to the one conference that did this.</li>
<li>Cover incidentals in the hotel. Yes, it’s a bit risky but come on, we’re not rockstars. We won’t screw you over. In most cases it will be a pretty small extra cost and it looks really good, it tells speakers you trust them and want them to have a good time.</li>
<li>Offer a speaker gift bag. It can contain all kinds of things: Stuff that will make their stay more comfortable (stain remover, travel toothbrush etc), souvenirs from the place since we rarely have time to do touristy stuff, alcohol for impromptu get togethers with other speakers, snacks to eat during a late night craving in the hotel room, anything goes and I’ve seen conferences put all kinds of stuff in there. It’s a nice welcome gesture. Bonus points if they’re personalized based on what you’ve researched about the speaker.</li>
<li>Send out a survey to the audience after the conference and <strong>let the speakers know how they did</strong>. Let them know what comments their talk got and how well they did compared to other speakers.</li>
</ul>
<p>Also, make sure you read <a href="http://quirksmode.org/coh/">PPK’s excellent Conference Organizer’s Handbook</a>.</p>
Slanted tabs with CSS 3D transforms2013-10-18T00:00:00Zhttps://lea.verou.me/?p=2252<p>Not sure if I’m the first to come up with this idea, but I searched and didn’t find anything. So, for a long time, I was wondering if there’s an easy way to create trapezoid shapes in CSS, especially with borders etc. Eventually, I realized that I could use a pseudo-element for the background and 3D rotate it, so that it appears like a trapezoid. Then <a href="https://twitter.com/krofdrakula">@krofdrakula</a> suggested on twitter that I could even add border-radius so that it looks like a tab, so I added that as well:</p>
<iframe src="https://dabblet.com/gist/6867917" width="100%" height="250"></iframe>
<p>Eventually I thought, why not actually turn this into a tab demo? So I made a dabblet with that. And then I realized that if you change the transform-origin, other interesting tab shapes appear! Enjoy:</p>
<iframe src="https://dabblet.com/gist/7039790" width="100%" height="700"></iframe>
<p>The best part? It degrades pretty gracefully on browsers that don’t support transforms! You get nice rounded tabs that just aren’t slanted (although they have a pretty large top padding, but you can use Modernizr for that. Try it for yourself by commenting the transform out in the dabblet and see the result.</p>
<p>Another issue is that the angled lines look a bit aliased in Firefox, but that’s a bug that will eventually get fixed.</p>
<p>In general, it’s a bit rough around the edges, so treat it more as a proof of concept. But with a little more work, it could totally work in production. Tested in Chrome, Safari, Firefox, IE9 (fallback) and IE10.</p>
Flexible Google-style loader with CSS2013-11-11T00:00:00Zhttps://lea.verou.me/?p=2261<p>So, for a while I had noticed the nice sutble loader Google apps use and I was wondering if it would be easy to make with CSS and CSS animations: <img src="https://maps.gstatic.com/tactile/omnibox/loading.gif" alt="Google’s loader" /></p>
<p>Yesterday, I realised that you can get this effect by increasing border size until about the middle of the element, as long as the total width stays the same (by using <code>box-sizing: border-box</code>):</p>
<iframe src="http://dabblet.com/gist/7408996" height="400" width="100%"></iframe>
<p>However, as you can see above, after the midpoint, the border is not curved any more, so does not produce the desired effect. However, what if we split the background colour in half, and animated <code>border-left</code> <em>until</em> 50% of the width and then <code>border-right</code> <em>from</em> 50% of the width? That worked, but only gave us 25% of the effect. I could recreate the whole effect by then animating border-top/bottom instead etc, but it’s easier to apply <code>animation-direction: alternate</code> to alternate between showing and hiding the circle and and simultaneously rotate the loader by 90deg each time, by applying <code>animation-timing-function: steps(4)</code> to a rotate animation that runs over 4x the duration of the border animation.</p>
<p>This is the finished result:</p>
<iframe src="http://dabblet.com/gist/7387255" height="300" width="100%"></iframe>
<p>The dimensions are all set in ems so that you can change the size in one place: Just change the font-size and the loader scales perfectly. It’s also accessible to screen reader users, as there is still text there.</p>
<p>And yes, it’s not super useful as-is, there are tons of spinners on the Web that you can use instead. However, I decided to post it (instead of just tweeting it) as I thought the techniques involved in making it might be interesting for some of you :)</p>
Cleanest CSS spinner, ever2013-11-29T00:00:00Zhttps://lea.verou.me/?p=2282<p>For some reason, I seem to have a fascination with CSS loaders these days. After <a href="http://lea.verou.me/2013/11/flexible-google-style-loader-with-css/">recreating the Google loader with clean CSS</a> recently, I set off to recreate the classic spinner with CSS. Yes, I know this has been done zillions of times, but I wanted a clean, maintainable, reusable solution, not just a proof of concept. Something with not tons of CSS and/or HTML elements.</p>
<p>I managed to recreate it with only 2 elements. I’m still not completely satisfied, as I was hoping to come up with a solution with just one element, but it’s still much better than all those solutions out there that use tons of elements and code.</p>
<p>So, how did I do it?</p>
<ul>
<li>I use the <code>::before</code> and <code>::after</code> pseudoelements of the parent and child div to create the 4 first bars</li>
<li>I use <code>box-shadow</code> with no blur on all four of the above to create the remaining 4 bars</li>
<li>I rotate the whole element with a <code>steps(8)</code> timing function to create the animation</li>
</ul>
<p>As with the Google-style loader, just changing the <code>font-size</code> on this scales the whole element, as everything is sized with ems. Also, there is fallback text, to make it accessible to screen readers. Tested in Chrome, Firefox, Safari, IE10. Should degrade gracefully on IE9 (spinner should look fine, just no animation).</p>
<p>Using a preprocessor for variables and calculations should simplify the code even further.</p>
<p>Enjoy :)</p>
<iframe src="http://dabblet.com/gist/7615212" width="100%" height="300"></iframe>
<p>Ideas for further improvement are welcome. Remember that it’s not just the size of the code that matters, but also its simplicity.</p>
CSS is for developers2013-12-05T00:00:00Zhttps://lea.verou.me/?p=2290<p>Quite often people assume that because the language I focus on is CSS, I <em>must</em> be a web designer. Don’t get me wrong, I love visual design with a passion. I have studied it a lot over the years and I’ve worked on several design projects for clients. Heck, I even have <a href="http://dribbble.com/LeaVerou">a dribbble profile</a>!</p>
<p>However, <strong>if I had to pick one role, I would definitely consider myself more of a developer than a designer.</strong> <a href="http://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/">I discovered coding on my own when I was 12</a> and so far it has been the most long lasting love of my life. Although I lost my coding virginity to Visual Basic (something I’m still embarrassed about), over the years I’ve coded in Java, C, C++, C#, PHP, JavaScript before I even got to CSS. I’ve actually studied Computer Science at university, graduated 4th in my class and I’m <a href="http://lea.verou.me/2014/02/im-going-to-mit/">gonna be doing research at MIT towards a PhD, starting fall 2014</a>. Regarding design, I’m completely self-taught. My personality is more similar to the developers I know than the designers I know. Coding comes naturally, but I have to struggle to get better at design. I’m a better developer than I will ever be a designer.</p>
<p>Still, the assumption often is that <em>I can’t possibly be a developer and interested in CSS, when there are all these amazing programming languages to focus my energy on instead. Therefore I must be a designer …right?</em> There are even people who know about <a href="https://github.com/LeaVerou/">my open source projects</a>, and still think that I can’t code in JavaScript or any other programming language (not sure how you can make most of these tools with pure CSS, but since <a href="http://eli.fox-epste.in/rule110-full.html">CSS is Turing complete</a>, I guess there must be a way!).</p>
<p>If you think I’m an exception, you’re mistaken. <a href="http://www.w3.org/Style/CSS/members.en.php3">Everyone else in the W3C CSS Working Group</a>, the group which defines the future of CSS, fits the profile of a developer much more than that of a designer. In fact, I might be the most designer-y person in it! Even outside the WG, the people I know who are really good at CSS, are either developers or hybrids (designers & developers).</p>
<p>This is no coincidence. <strong>The skills required to write good CSS code are by and large the same skills required to write good code in general.</strong> CSS code also needs to be <a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a>, maintainable, flexible etc. CSS might have a visual output, but is still code, just like SVG, WebGL/OpenGL or the JavaScript Canvas API. It still requires the same kind of analytical thinking that programming does. Especially these days that most people use preprocessors for their CSS, with variables, math, conditionals and loops, it’s almost starting to look like programming!</p>
<p>I find it counter-productive that CSS in most jobs is assigned to designers. Designers should be doing what they do best and love: Design. Sure, they should be aware of the practical limitations of the medium and should be able to read and lightly edit CSS or hack together a prototype to show how their design behaves in different conditions, but it shouldn’t be their job to write CSS for production. The talents required to be a good designer and a good coder are very different and it’s unreasonable to expect both from everyone. Also, when you know you’re gonna have to implement the design you’re working on, it’s tempting to produce designs that can be easily converted to CSS, instead of pushing the boundaries. We don’t usually expect developers to design, even though it’s an added bonus when they have an eye for design as well. It should be the same for designers.</p>
<p>And if you’re a designer who writes amazing CSS and is about to tell me off in the comments, hold your horses. <strong>I’m not saying you shouldn’t be coding CSS.</strong> I’m saying that <strong>if you’re good at it, it means you’re both</strong> a designer AND a developer. Own it! :D</p>
Simple script: Automatic talks list2013-12-28T00:00:00Zhttps://lea.verou.me/?p=2301<p>I guess this will not be useful to many, but thought I’d open source it anyway, in case someone else finds it useful. I mostly wrote it because after 50+ conferences, I got seriously fed up updating <a href="http://lea.verou.me/speaking">the list</a> by editing the HTML manually. Now I will only have to edit <a href="https://projects.verou.me/talks/talks.json">a JSON file</a> manually! :P</p>
<p>Admittedly, it’s not my best code (more like a quick hack), but it should be pretty easy to adapt to your needs, even to adapt it to lists of different things instead of talks. Using it for talks is very straight forward: Include the CSS and JS, add a list with a class of “talks” where you want it, done.</p>
<p>Hope you enjoy it :)</p>
<p>Links: <a href="http://lea.verou.me/speaking/">Live demo</a> | <a href="https://github.com/LeaVerou/talks">Github repo</a> | <a href="https://projects.verou.me/talks/">Script page</a></p>
Smooth state animations with animation-play-state2014-01-09T00:00:00Zhttps://lea.verou.me/?p=2315<p>When a CSS animation is applied from the beginning of the page load, things are easy. You just use the animation property with appropriate parameters, and you’re done. However, what if the animation is applied on a certain state, e.g. :hover, :active, :focus or a JS-triggered class change?</p>
<p>A naïve approach would be to try something like this:</p>
<iframe src="http://dabblet.com/gist/8334474" width="100%" height="400"></iframe>
<p>However, this means that when you hover out of the element, it abruptly snaps to its original state (no rotation). In many cases, it would be a more desirable to have it freeze in the last shown frame, until we hover over it again. To achieve that, we can apply the animation from the beginning, with <code>animation-play-state: paused;</code> and just change it on :hover to <code>animation-play-state: running;</code>. This is what happens then:</p>
<iframe src="http://dabblet.com/gist/8333352" width="100%" height="400"></iframe>
<p>I figured this out when I was recently helping my good friend <a href="http://twitter.com/juliancheal">Julian</a> with <a href="http://juliancheal.co.uk/">his one page website</a>*. When you hover over the figure, it starts scrolling, but when you hover out of it, it doesn’t snap back to its original position, which would’ve looked awful.</p>
<p>*Beware it’s still a bit rough around the edges, e.g. the result has some rendering bugs on Firefox & IE plus some unsupported features messing it up (e.g. baseline-shift in SVG), but those are for another day as I had work to do and this ended up taking longer than the few hours I expected. Beyond the animation, you might want to explore the CSS-only <em>buttons</em> (see what I did there?) or the leather figure frame. Credits to <a href="https://twitter.com/laurakalbag">Laura Kalbag</a> for the tweed background & color scheme. I also experimented with SASS on this one and found it much smoother to work with than LESS, so I might stick with it for those cases where I need a preprocessor.</p>
<p><a href="http://juliancheal.co.uk/"><img src="https://lea.verou.me/2014/01/smooth-state-animations-with-animation-play-state/images/Screen-Shot-2014-01-09-at-14.45.40--1024x558.png" alt="Screenshot" /></a></p>
Vote for me in the net awards 2014!2014-01-30T00:00:00Zhttps://lea.verou.me/?p=2334<p><a href="https://thenetawards.com/vote/contribution/lea-verou/"><img src="http://future-publishing.msgfocus.com/files/amf_future_publishing/project_270/net-awards-2014-nominee-ribbon.png" alt="" /></a>I was excited and surprised to find out I’ve been in the 10 finalists for two (2!) categories in the net awards this year:</p>
<ul>
<li><a href="https://thenetawards.com/vote/contribution/lea-verou/">Outstanding contribution</a></li>
<li><a href="https://thenetawards.com/vote/talk/lea-verou-the-humble-border-rad/">Conference Talk of the Year</a> (for The humble border-radius)</li>
</ul>
<p>You can vote for me by clicking the above links and have my eternal gratitude :-)</p>
<p>Furthermore, the CERN line mode browser project, of which <a href="http://line-mode.cern.ch/interviews/">I was a part of</a>, has been <a href="https://thenetawards.com/vote/collaboration/line-mode-browser-2013/">nominated in “Best Collaborative Project”</a>!</p>
<p>I’ve given “The humble border-radius” more than once, so you can pick one to watch below (in the net awards page they’ve linked to the FOWD London one):</p>
<ul>
<li><a href="http://vimeo.com/70171266">CSSDay</a></li>
<li><a href="http://cssconf.com/talk-verou.html">CSSConf</a></li>
<li><a href="http://www.futureinsights.com/home/lea-verou-keynote-the-humble-border-radius.html">FOWD London</a></li>
<li><a href="http://vimeo.com/67567648">Front-Trends</a></li>
<li><a href="http://www.youtube.com/watch?v=JSaMl2OKjfQ">Abridged version (only 15 minutes!) from this year’s O’Reilly Fluent</a></li>
</ul>
<p>I would recommend the CSSDay one, as it changes every time and that one is newer.</p>
<p>Thanks again to anyone who nominated me and everyone who votes for me. You’re all wonderful. <3</p>
Introducing Whathecolor: A color game for web developers!2014-02-08T00:00:00Zhttps://lea.verou.me/?p=2342<p>I’ve been interested in digital color for a long time, and this year I decided to risk giving a technical talk about color some of the conferences I’m speaking at. “Why is that risky?” you might ask. Well, it might end up being really interesting, or it may end up alienating both designers because it’s too technical and developers because it’s about a “designery” topic.</p>
<p>In preparation for this talk, I decided to make a simple game to see how well I and other web developers understand color, and especially CSS notations of color. Meet <a href="https://projects.verou.me/whathecolor">Whathecolor</a>!</p>
<p>The idea is simple: You are presented with a color and you try to type in a CSS color that matches it. It could be anything, from hsl() or rgb() to even named colors (although that would be stupid). It would be interesting to see what averages people get by trying hsl() vs rgb() and whether the former is as easier for web developers as we think. Feel free to post your results here or on twitter! Perhaps in the future, something like this could be used by the CSS WG to test the usability of color notations we’re thinking of adding to CSS instead of speculating about it.</p>
<p>Disclaimer: This is a quick hack. Please don’t complain that it doesn’t look great on your phone and stuff like that.</p>
<p>Also, yes, if you want to cheat, it’s super easy, but I have no idea why somebody would cheat on something like this.</p>
<p><a href="https://projects.verou.me/whathecolor">Play</a></p>
<h3 id="color-proximity" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2014/02/whathecolor-a-color-game-for-web-developers/#color-proximity">Color proximity</a></h3>
<p>A challenging part in developing this was calculating the proximity of two colors to show the user how close they are getting. My first thought was to use the Euclidean distance of the two colors in the RGB cube and divide it by the maximum distance the color could have from any other RGB color. However, this proved out to be inaccurate in many cases, probably due to the lack of perceptual uniformity in RGB. As an example, try <code>#f0f</code> and <code>#ff80ff</code>. Although they are quite similar visually, the reported proximity was around 66% (1 - 128/382).</p>
<p>So I researched existing algorithms to get the proximity of two colors. Like most things color-related, it looks like <a href="http://en.wikipedia.org/wiki/Color_difference">Color Difference</a> is not quite as simple as I thought, and is considered a topic of interest in Color Science. However, converting to L*a*b* and using the CIE94 and CIEDE2000 formulas seemed a bit of an overkill for this and I wasn’t terribly impressed with the CIE76 formula after trying the results out <a href="http://colormine.org/delta-e-calculator/">online</a> for some sample pairs (e.g. it gives ~60% for the aforementioned pair, which is even lower than what I got with my naïve RGB method!).</p>
<p>So I experimented a bit and ended up using an average of my original idea and a sum of the HSL differences (divided by the max differences), which seems to work relatively ok. There are still cases where it’s off, but ho hum. After all, the proximity is mainly useful when you get close enough to the color (>90%), as until then you tend to play it by eye. Any improvements on the algorithm used are welcome. Or if enough people think it’s not working very well, I’ll bite the bullet and end up using DeltaE.</p>
<h3 id="other-notes" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2014/02/whathecolor-a-color-game-for-web-developers/#other-notes">Other notes</a></h3>
<ul>
<li>You do not need a proximity of 100% to win, since rounding errors might prevent you from matching the exact color if you’re using HSL. Also, because matching the exact same color isn’t really important, as long as you get close enough that any difference is imperceptible.</li>
<li>I wrote a Color “class” for this, which you can find in <a href="https://projects.verou.me/whathecolor/color.js">color.js</a>. Like most of my open source stuff, it’s MIT licensed. Maybe it could be useful in some other color-related project, who knows.</li>
<li>My original idea was to have “levels”, where the color would get increasingly more difficult to get. For example, in the first level, you’d only have to guess simple colors whose RGB coordinates were either 0, 128 or 255. So, my Color.random() method accepts an entropy parameter, for that level. However, when I tested the game with truly random colors (any integer from 0 to 255), it turned out it wasn’t really that hard (it took me about a minute to guess each color), so I ditched the idea of levels early on. The code is still there though.</li>
<li>An idea about making it harder in the future would be to introduce semi-transparent (RGBA/HSLA) colors. That would be fun :evil_grin:</li>
</ul>
<p><a href="https://lea.verou.me/2014/02/whathecolor-a-color-game-for-web-developers/images/Screen-Shot-2014-02-08-at-02.36.12-.png"><img src="https://lea.verou.me/2014/02/whathecolor-a-color-game-for-web-developers/images/Screen-Shot-2014-02-08-at-02.36.12-.png" alt="Screenshot" /></a>PS: The times in this screenshot aren’t real, I wanted to take one quickly, so I used the dev tools.</p>
I’m going to MIT!!2014-02-11T00:00:00Zhttps://lea.verou.me/?p=2357<p>Last year, I did something crazy, that I’ve been wanting to do since I was little: I applied to MIT’s PhD program in Electrical Engineering and Computer Science.</p>
<p><img src="https://lea.verou.me/2014/02/im-going-to-mit/images/Screen-Shot-2014-02-11-at-18.25.21-.png" alt="One of the letters" /></p>
<p>It was not only crazy because I have been working for several years already, but also because I only applied to MIT, as I decided I did not want to go to any other university, both for pragmatic and emotional reasons. As any prospective grad student will tell you, applying to only one top university is recipe for failure. I didn’t tell many people, but everyone who knew thought I’d get in — except me. You see, I wasn’t a typical candidate. Sure, I have done lots of things I’m proud of, but I didn’t have an amazing GPA or publications in prestigious academic conferences.</p>
<p>It felt like a very long shot, so you can imagine my excitement when I received the letters of acceptance, about a week ago. I will remember that moment forever. I was watching Breaking Bad, feeling miserable over a breakup that happened only a few hours earlier. About a minute into the episode (s05e09), I saw an email notification titled “Your application to MIT EECS”. My first thought was that there was some problem with my application. And then I read the first few lines:</p>
<blockquote>
<p>Dear Michailia Verou:</p>
<p>If you have not already heard from them, you will shortly receive a letter from the EECS department at MIT, informing you that you have been admitted to the graduate program in Computer Science at MIT next fall. Congratulations!!</p>
</blockquote>
<p>WHAAAA? Was it a scam? But then, how did they have all my details? Holy mother of the Flying Spaghetti Monster, I got in!!! Soon thereafter, a letter from <a href="http://www.csail.mit.edu/">CSAIL</a> followed (where I said I wanted to work, specifically in the <a href="http://groups.csail.mit.edu/uid/">UID</a>), and then even more letters. I started calling everyone who knew I applied to share the news, though it proved quite hard to form sentences instead of uncontrollably screaming in joy. I was (and am!) so excited about the future, that it completely overshadows any other life problems (at least for the time being).</p>
<p>Of course, my happiness is mixed with sheer terror. I keep worrying that I will be the dumbest person in the room, or that I don’t remember as much from my undergrad studies as the others will. I’m even terrified of meeting my future advisor(s) in case getting to know me better makes them wonder why I was accepted. But I try to remind myself about <a href="http://en.wikipedia.org/wiki/Impostor_syndrome">impostor syndrome</a>, and from what I’ve read in forums & blogs, it seems that I’m not alone in having such fears.</p>
<p>I held off blogging about it until I felt I was able to write something coherent, but I can’t wait to share my excitement any longer.</p>
<p>To the future!</p>
<p>To real life plot twists!</p>
<p>To MIT!</p>
<p>Boy, I’m thrilled. :D</p>
Dynamically generated SVG through SASS + A 3D animated RGB cube!2014-04-04T00:00:00Zhttps://lea.verou.me/?p=2389<p><a href="https://projects.verou.me/chroma-zone/rgb-cube.html"><img src="https://lea.verou.me/2014/04/dynamically-generated-svg-through-sass-a-3d-animated-rgb-cube/images/cube-screenshot.png" alt="Screenshot of the cube" /></a>Today, I was giving the opening keynote at <a href="http://codemania.co.nz/">Codemania</a> in Auckland, New Zealand. It was a talk about color from a math/dev perspective. It went quite well, despite my complete lack of sleep. I mean that quite literally: I hadn’t slept all night. No, it wasn’t the jetlag or the nervousness that kept me up. It was my late minute decision to replace the <a href="https://projects.verou.me/chroma-zone/img/rgb-cube.jpg">static, low-res image of an RGB cube</a> I was using until then with a <strong><a href="https://projects.verou.me/chroma-zone/rgb-cube.html">3D cube generated with CSS and animated with CSS animations</a></strong>. Next thing I knew, it was light outside and I had to start getting ready. However, I don’t regret literally losing sleep to make a slide that is only shown for 20 seconds at most. Not only it was super fun to develop, but also yielded a few things that I thought were interesting enough to blog about.</p>
<p><strong>The most challenging part wasn’t actually the 3D cube.</strong> This has been done tons of times before, it was probably the most common demo for CSS 3D transforms a couple of years ago. The only part of this that could be of interest is that mine only used 2 elements for the cube. This is a dabblet of the cube, without any RGB gradients on it:</p>
<iframe src="http://dabblet.com/gist/9594360" height="800" width="100%"></iframe>
<p><strong>The challenging part was creating the gradients for the 6 sides.</strong> These are not plain gradients, as you can see below:</p>
<p><a href="https://lea.verou.me/2014/04/dynamically-generated-svg-through-sass-a-3d-animated-rgb-cube/images/gradients.png"><img src="https://lea.verou.me/2014/04/dynamically-generated-svg-through-sass-a-3d-animated-rgb-cube/images/gradients.png" alt="RGB cube sides" /></a>These are basically two linear gradients from left to right, with the topmost one being masked with a gradient from top to bottom. You can use <a href="http://dabblet.com/gist/9201622">CSS Masking to achieve this (for Chrome/Safari)</a> and SVG Masks for Firefox, but this masks the whole element, which would hide the pseudo-elements needed for the sides. What I needed was masks applied to backgrounds only, not the whole element.</p>
<p>It seemed obvious that the best idea would be to use SVG background images. For example, here is <a href="http://dabblet.com/gist/9964928">the SVG background needed for the top left one</a>:</p>
<pre><code class="language-markup"><svg xmlns="http://www.w3.org/2000/svg" width="200px" height="200px">
<linearGradient id="yellow-white" x1="0" x2="0" y1="0" y2="1">
<stop stop-color="yellow" />
<stop offset="1" stop-color="white" />
</linearGradient>
<linearGradient id="magenta-red" x1="0" x2="0" y1="0" y2="1">
<stop stop-color="red" />
<stop offset="1" stop-color="magenta" />
</linearGradient>
<linearGradient id="gradient" x1="0" x2="1" y1="0" y2="0">
<stop stop-color="white" />
<stop offset="1" stop-color="black" />
</linearGradient>
<mask id="gradient-mask">
<rect width="100%" height="100%" fill="url(#gradient)"/>
</mask>
<rect width="100%" height="100%" fill="url(#yellow-white)"/>
<rect width="100%" height="100%" fill="url(#magenta-red)" mask="url(#gradient-mask)"/>
</svg>
</code></pre>
<p>However, I didn’t want to have 6 separate SVG files, especially with this kind of repetition (cross-linking to reuse gradients and masks across different files is still fairly buggy in certain browsers). I wanted to be able to edit this straight from my CSS. And then it hit me: I was using SASS already. I could code SASS functions that generate SVG data URIs!</p>
<p>Here’s the set of SVG generating SASS functions I ended up writing:</p>
<pre><code class="language-scss">@function inline-svg($content, $width: $side, $height: $side) {
@return url('data:image/svg+xml,#{$content}');
}
@function svg-rect($fill, $width: '100%', $height: $width, $x: '0', $y: '0') {
@return unquote('');
}
@function svg-gradient($id, $color1, $color2, $x1: 0, $x2: 0, $y1: 0, $y2: 1) {
@return unquote('
');
}
@function svg-mask($id, $content) {
@return unquote('#{$content}');
}
</code></pre>
<p>And then I was able to generate each RGB plane with another function that made use of them:</p>
<pre><code class="language-scss">@function rgb-plane($c1, $c2, $c3, $c4) {
@return inline-svg(
svg-gradient('top', $c1, $c2) +
svg-gradient('bottom', $c3, $c4) +
svg-gradient('gradient', white, black, 0, 1, 0, 0) +
svg-mask('gradient-mask', svg-rect('url(%23gradient)')) +
svg-rect('url(%23bottom)') +
svg-rect('url(%23top)" mask="url(%23gradient-mask)')
);
}
/* ... */
.cube {
background: rgb-plane(blue, black, aqua, lime);
&::before {
background: rgb-plane(blue, fuchsia, aqua, white);
}
&::after {
background: rgb-plane(fuchsia, red, blue, black);
}
}
.cube .sides {
background: rgb-plane(yellow, lime, red, black);
&::before {
background: rgb-plane(yellow, white, red, fuchsia);
}
&::after {
background: rgb-plane(white, aqua, yellow, lime);
}
}
</code></pre>
<p>However, the same functions can be used for all sorts of SVG backgrounds and it’s very easy to add a new one. E.g. to make polygons:</p>
<pre><code class="language-scss">@function svg-polygon($fill, $points) {
@return unquote('');
}
@function svg-circle($fill, $r: '50%', $cx: '50%', $cy: '50%') {
@return unquote('');
}
</code></pre>
<p>You can see <a href="https://projects.verou.me/chroma-zone/rgb-cube.scss">the whole SCSS file here</a> and <a href="https://projects.verou.me/chroma-zone/rgb-cube.css">its CSS output here</a>.</p>
<p><strong>Warning:</strong> Keep in mind that IE9 and some older versions of other browsers have issues with unencoded SVG data URIs. Also, you still need to escape hashes (<code>%23</code> instead of <code>#</code>), otherwise Firefox fails.</p>
Image comparison slider with pure CSS2014-07-25T00:00:00Zhttps://lea.verou.me/?p=2423<p>As a few of you know, I have been spending a good part of this year writing a book for O’Reilly called “CSS Secrets” (<a href="http://shop.oreilly.com/product/0636920031123.do">preorder here!</a>). I wanted to include a “secret” about the various uses of the <code>resize</code> property, as it’s one of my favorite underdogs, since it rarely gets any love. However, just mentioning the typical use case of improving the UX of text fields didn’t feel like enough of a secret at all. The whole purpose of the book is to get authors to think outside the box about what’s possible with CSS, not to recite widely known applications of CSS features. So I started brainstorming: What else could we do with it?</p>
<p>Then I remembered <a href="http://demosthenes.info/blog/819/A-Before-And-After-Image-Comparison-Slide-Control-in-HTML5">Dudley’s awesome Before/After image slider from a while ago</a>. While I loved the result, the markup isn’t great and it requires scripting. Also, both images are CSS backgrounds, so for a screen reader, there are no images there. And then it dawned on me: What if I overlaid a <code><div></code> on an image and made it horizontally resizable through the <code>resize</code> property? I tried it, and as you can see below, it worked!</p>
<iframe src="https://dabblet.com/gist/25fa1985bb9f1633c86e" width="100%" height="900"></iframe>
<p>The good parts:</p>
<ul>
<li>More semantic markup (2 images & 2 divs). If <code>object-fit</code> was widely <a href="http://caniuse.com/#feat=object-fit">supported</a>, it could even be just one div and two images.</li>
<li>No JS</li>
<li>Less CSS code</li>
</ul>
<p>Of course, few things come with no drawbacks. In this case:</p>
<ul>
<li>One big drawback is keyboard accessibility. Dudley’s demo uses a range input, so it’s keyboard accessible by design.</li>
<li>You can only drag from the bottom right corners. In Dudley’s demo, you can click at any point in the slider. And yes, I did try to style ::webkit-resizer and increase its size so that at least it has smoother UX in Webkit. However, no matter what I tried, nothing seemed to work.</li>
</ul>
<p>Also, none of the two seems to work on mobile.</p>
<p>It might not be perfect, but I thought it’s a pretty cool demo of what’s possible with the <code>resize</code> property, as everybody seems to only use it in textareas and the like, but its potential is much bigger.</p>
<p>And now if you’ll excuse me, I have a chapter to write ;)</p>
<p><strong>Edit:</strong> It looks like <a href="http://codepen.io/Kseso/pen/dyeBL/">somebody figured out a similar solution</a> a few months ago, which does manage to make the resizer full height, albeit with less semantic HTML and more flimsy CSS. The main idea is that you use a separate element for the resizing (in this case a textarea) with a height of 15px = the height of the resizer. Then, they apply a scaleY() transform to stretch that 15px to the height of the image. Pretty cool! Unfortunately, it requires hardcoding the image size in the CSS.</p>
An easy notation for grayscale colors2014-07-27T00:00:00Zhttps://lea.verou.me/?p=2436<p>These days, there is <a href="http://lists.w3.org/Archives/Public/www-style/2014Jul/0432.html">a lengthy discussion in the CSS WG about how to name a function that produces shades of gray</a> (from white to black) with varying degrees of transparency, and we need your feedback about which name is easier to use.</p>
<p>The current proposals are:</p>
<h3 id="1.-gray(lightness-%5B%2C-alpha%5D)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2014/07/an-easy-notation-for-grayscale-colors/#1.-gray(lightness-%5B%2C-alpha%5D)">1. gray(lightness [, alpha])</a></h3>
<p>In this proposal gray(0%) is black, gray(50%) is gray and gray(100%) is white. It also accepts numbers from 0-255 which correspond to rgb(x,x,x) values, so that gray(255) is white and gray(0) is black. It also accepts an <strong>optional second argument for alpha transparency</strong>, so that gray(0, .5) would be equivalent to rgba(0,0,0,.5).</p>
<p>This is the naming of the function in the current <a href="http://dev.w3.org/csswg/css-color/#grays">CSS Color Level 4 draft</a>.</p>
<h3 id="2.-white(lightness-%5B%2C-alpha%5D)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2014/07/an-easy-notation-for-grayscale-colors/#2.-white(lightness-%5B%2C-alpha%5D)">2. white(lightness [, alpha])</a></h3>
<p>Its arguments work in the same way as gray(), but it’s consistent with the expectation that function names that accept percentages give the “full effect” at 100%. gray(100%) sounds like a shade of gray, when it’s actually white. white(100%) is white, which might be more consistent with author expectations. Of course, this also accepts alpha transparency, like all the proposals listed here.</p>
<h3 id="3.-black(lightness-%5B%2C-alpha%5D)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2014/07/an-easy-notation-for-grayscale-colors/#3.-black(lightness-%5B%2C-alpha%5D)">3. black(lightness [, alpha])</a></h3>
<p>black() would work in the opposite way: black(0%) would be white, black(100%) would be black and black(50%,.5) would be semi-transparent gray. The idea is that people are familiar thinking that way from grayscale printing.</p>
<h3 id="4.-rgb()-with-one-argument-and-rgba()-with-two-arguments" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2014/07/an-easy-notation-for-grayscale-colors/#4.-rgb()-with-one-argument-and-rgba()-with-two-arguments">4. rgb() with one argument and rgba() with two arguments</a></h3>
<p>rgb(x) would be a shorthand to rgb(x, x, x) and rgba(x, y) would be a shorthand to rgba(x, x, x, y). So, rgb(0) would be black and rgb(100%) or rgb(255) would be white. The benefit is that authors are already accustomed to using rgb() for colors, and this would just be a shortcut. However, note how you will need to change the function name to get a semi-transparent version of the color. Also, if in the future one needs to change the color to not be a shade of gray, a function name change is not needed.</p>
<p>I’ve written <a href="http://sassmeister.com/gist/20ac0049428ccfcbe8f1">some SCSS to emulate these functions</a> so you can play with them in your stylesheets and figure out which one is more intuitive. Unfortunately rgb(x)/rgba(x,a) cannot be polyfilled in that way, as that would overwrite the native rgb()/rgba() functions. Which might be an argument against them, as being able to polyfill through a preprocessor is quite a benefit for a new color format IMO.</p>
<p>You can <a href="https://docs.google.com/forms/d/1pp3RY-A4MAs7b-gmqFx6bKn52_G_WLoPFkV0vueiWP4/viewform?usp=send_form">vote here</a>, but that’s mainly for easy vote counting. It’s strongly encouraged that you also leave a comment justifying your opinion, either here or in the list.</p>
<p><a href="https://docs.google.com/forms/d/1pp3RY-A4MAs7b-gmqFx6bKn52_G_WLoPFkV0vueiWP4/viewform?usp=send_form">Vote now!</a></p>
<p>Also <strong>tl;dr</strong> If you can’t be bothered to read the post and understand the proposals well, please, refrain from voting.</p>
Awesomplete: 2KB autocomplete with zero dependencies2015-02-19T00:00:00Zhttps://lea.verou.me/?p=2474<p><a href="https://projects.verou.me/awesomplete"><img src="https://lea.verou.me/2015/02/awesomplete-2kb-autocomplete-with-zero-dependencies/images/awesomplete-300x248.png" alt="awesomplete" /></a>Sorry for the lack of posts for the past 7 (!) months, I’ve been super busy working on <a href="http://shop.oreilly.com/product/0636920031123.do">my book</a>, which up to a certain point, I couldn’t even imagine finishing, but I’m finally there! I’ve basically tried to cram all the CSS wisdom I’ve accumulated over the years in it :P (which is partly why it took so long, I kept remembering more things that just *had* to be in it. Its page count on the O’Reilly website had to be updated 3 times, from 250 to 300 to 350 and it looks like the final is gonna be closer to 400 pages) and it’s gonna be super awesome (<a href="http://shop.oreilly.com/product/0636920031123.do">preorder here!</a>) :D . I have been posting a few CSS tricks now and then on <a href="http://twitter.com/leaverou">my twitter account</a>, but haven’t found any time to write a proper blog post.</p>
<p>Anyhow, despite being super busy with MIT (which btw is amazing, challenging in a good way, and full of fantastic people. So glad to be here!) and the book, I recently needed an autocomplete widget for something. Surprisingly, I don’t think I ever had needed to choose one in the past. I’ve worked with apps that had it, but in those cases it was already there.</p>
<p>At first, I didn’t fret. Finally, a chance to use the <a href="http://blog.teamtreehouse.com/creating-autocomplete-dropdowns-datalist-element">HTML5 <code><datalist></code></a>, so exciting! However, the more I played with it, the more my excitement was dying a slow death, taking my open web standards dreams and hopes along with it. Not only it’s incredibly inconsistent across browsers (e.g. Chrome matches only from the start, Firefox anywhere!), it’s also not hackable or customizable in any way. Not even if I got my hands dirty and used proprietary CSS, I still couldn’t do anything as simple as changing how the matching happens, styling the dropdown or highlighting the matching text!</p>
<p>So, with a heavy heart, I decided to use a script. However, when I looked into it, everything seemed super bloated for my needs and anything with half decent usability required jQuery, which results in even more bloat.</p>
<p>So, I did what every crazy person with a severe case of <a href="http://en.wikipedia.org/wiki/Not_invented_here">NIH Syndrome</a> would: <strong>I wrote one</strong>. It was super fun, and I don’t regret it, although now I’m even more pressed for time to meet my real deadlines. I wrote it primarily for myself, so even if nobody else uses it, ho hum, it was more fun than alternative ways to take a break. However, it’s my duty to put it on Github, in case someone else wants it and in case the community wants to take it into its loving, caring hands and pull request the hell out of it.</p>
<p>To be honest, I think it’s both pretty and pretty useful and even though it won’t suit complex needs out of the box, it’s pretty hackable/extensible. I even wrote quite a bit of <a href="https://projects.verou.me/awesomplete/">documentation</a> at some point this week when I was too sleepy to work and not sufficiently sleepy to sleep — because apparently that’s what was missing from my life: even more technical writing.</p>
<p>I saved the best for last: It’s so lightweight you might end up chasing it around if there’s a lot of wind when you download it. It’s currently <strong>a little under 1.5KB</strong> minified & gzipped (the website says 2KB because it will probably grow with commits and I don’t want to have to remember to update it all the time), with <strong>zero dependencies</strong>! :D</p>
<p>And it’s even been verified to work in <strong>IE9 (sorta), IE10+, Chrome, Firefox, Safari 5+, Mobile Safari</strong>!</p>
<p><a href="https://projects.verou.me/awesomplete">’Nuff said. Get it now!</a></p>
<p>PS: If you’re about to leave a comment on how it’s not called “<em>autocomplete</em>”, but “<em>typeahead</em>”, please go <a href="http://tirania.org/blog/archive/2011/Feb-17.html">choke on a bucket of cocks</a> instead. :P</p>
jQuery considered harmful2015-04-19T00:00:00Zhttps://lea.verou.me/?p=2511<p>Heh, I always wanted to do one of those “X considered harmful” posts*. :D</p>
<p>Before I start, let me say that I think <strong>jQuery has helped tremendously to move the Web forward</strong>. It gave developers power to do things that were previously unthinkable, and pushed the browser manufacturers to implement these things natively (without jQuery we probably wouldn’t have <code>document.querySelectorAll</code> now). And jQuery is still needed for those that cannot depend on the goodies we have today and have to support relics of the past like IE8 or worse.</p>
<p>However, as much as I feel for these poor souls, they are the minority. There are tons of developers that don’t need to support old browsers with a tiny market share. And let’s not forget those who aren’t even Web professionals: Students and researchers not only don’t need to support old browsers, but can often get by just supporting a single browser! You would expect that everyone in academia would be having tons of fun using all the modern goodies of the Open Web Platform, right? And yet, I haven’t seen jQuery being so prominent anywhere else as much as it is in academia. Why? Because this is what they know, and they really don’t have the time or interest to follow the news on the Open Web Platform. They don’t know what they need jQuery for, so they just use jQuery anyway. However, being able to do these things natively now is not the only reason I’d rather avoid jQuery.</p>
<h3 id="yes%2C-you-probably-don%E2%80%99t-really-need-it%E2%80%A6" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2015/04/jquery-considered-harmful/#yes%2C-you-probably-don%E2%80%99t-really-need-it%E2%80%A6">Yes, you probably don’t really need it…</a></h3>
<p>I’m certainly not the first one to point out how much of jQuery usage is about things you can do natively, so I won’t spend time repeating what others have written. Just visit the following and dive in:</p>
<ul>
<li><a href="http://youmightnotneedjquery.com/">You might not need jQuery</a></li>
<li><a href="http://blog.garstasio.com/you-dont-need-jquery/">You don’t need jQuery!</a></li>
<li><a href="http://www.sitepoint.com/do-you-really-need-jquery/">Do you really need jQuery?</a></li>
<li><a href="http://tutorialzine.com/2014/06/10-tips-for-writing-javascript-without-jquery/">10 tips for writing JavaScript without jQuery</a></li>
<li>…and lots more. Just try <a href="https://www.google.com/search?q=you+don%27t+need+jquery">googling “you don’t need jQuery”</a> and you will find plenty.</li>
</ul>
<p>I will also not spend time talking about <strong>file size</strong> or how much <strong>faster</strong> native methods are. These have been talked about before. Today, I want to make a point that is not frequently talked about…</p>
<h3 id="%E2%80%A6but-that%E2%80%99s-not-even-the-biggest-reason-not-to-use-it-today" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2015/04/jquery-considered-harmful/#%E2%80%A6but-that%E2%80%99s-not-even-the-biggest-reason-not-to-use-it-today">…but that’s not even the biggest reason not to use it today</a></h3>
<p>To avoid extending the native element prototypes, jQuery uses <strong>its own wrapper objects</strong>. Extending native objects in the past was a huge no-no, not only due to potential collisions, but also due to memory leaks in old IE. So, what is returned when you run <code>$("div")</code> is not a reference to an element, or a NodeList, it’s a jQuery object. This means that a jQuery object has completely different methods available to it than a reference to a DOM element, an array with elements or any type of NodeList. However, these native objects come up all the time in real code — as much as jQuery tries to abstract them away, you always have to deal with them, even if it’s just wrapping them in $(). For example, the context when a callback is called via jQuery’s .bind() method is a reference to an HTML element, not a jQuery collection. Not to mention that often you use code from multiple sources — some of them assume jQuery, some don’t. Therefore, you always end up with <strong>code that mixes jQuery objects, native elements and NodeLists</strong>. And this is where the hell begins.</p>
<p>If the developer has followed a naming convention for which variables contain jQuery objects (prepending the variable names with a dollar sign is the common one I believe) and which contain native elements, this is less of a problem (humans often end up forgetting to follow such conventions, but let’s assume a perfect world here). However, in most cases no such convention is followed, which results in the code being incredibly hard to understand by anyone unfamiliar with it. Every edit entails a lot of trial and error now (“Oh, it’s not a jQuery object, I have to wrap it with <code>$()</code>!” or “Oh, it’s not an element, I have to use [0] to get an element!”). To avoid such confusion, developers making edits often end up wrapping anything in <code>$()</code> defensively, so throughout the code, the same variable will have gone through <code>$()</code> multiple times. For the same reason, it also becomes especially hard to refactor jQuery out of said code. You are essentially <strong>locked in</strong>.</p>
<p>Even if naming conventions have been followed, you can’t just deal only with jQuery objects. You often need to use a native DOM method or call a function from a script that doesn’t depend on jQuery. Soon, conversions to and from jQuery objects are all over the place, cluttering your code.</p>
<p>In addition, when you add code to said codebase, you usually end up wrapping every element or nodelist reference with <code>$()</code> as well, because you don’t know what input you’re getting. So, not only you’re locked in, but <strong>all future code you write for the same codebase is also locked in</strong>.</p>
<p>Get any random script with a jQuery dependency that you didn’t write yourself and try to refactor it so that it doesn’t need jQuery. I dare you. You will see that your main issue will not be how to convert the functionality to use native APIs, but understanding what the hell is going on.</p>
<h3 id="a-pragmatic-path-to-js-nudity" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2015/04/jquery-considered-harmful/#a-pragmatic-path-to-js-nudity">A pragmatic path to JS nudity</a></h3>
<p>Sure, many libraries today require jQuery, and like I recently <a href="https://twitter.com/leaverou/status/588504217410609152">tweeted</a>, avoiding it entirely can feel like you’re some sort of digital vegan. However, this doesn’t mean you have to use it yourself. Libraries can always be replaced in the future, when good non-jQuery alternatives become available.</p>
<p>Also, most libraries are written in such a way that they do not require the $ variable to be aliased to jQuery. Just call <a href="https://api.jquery.com/jquery.noconflict/">jQuery.noConflict()</a> to reclaim the $ variable and be able to assign it to whatever you see fit. For example, I often define these helper functions, inspired from the <a href="https://developer.chrome.com/devtools/docs/commandline-api#selector">Command Line API</a>:</p>
<pre><code class="language-javascript">// Returns first element that matches CSS selector {expr}.
// Querying can optionally be restricted to {container}’s descendants
function $(expr, container) {
return typeof expr === "string"? (container || document).querySelector(expr) : expr || null;
}
// Returns all elements that match CSS selector {expr} as an array.
// Querying can optionally be restricted to {container}’s descendants
function $$(expr, container) {
return [].slice.call((container || document).querySelectorAll(expr));
}
</code></pre>
<p>In addition, I think that having to type <code>jQuery</code> instead of <code>$</code> every time you use it somehow makes you think twice about superfluously using it without really needing to, but I could be wrong :)</p>
<p>Also, if you actually <strong>like</strong> the jQuery API, but want to avoid the bloat, consider using <a href="http://zeptojs.com/">Zepto</a>.</p>
<p>* I thought it was brutally obvious that the title was tongue-in-cheek, but hey, it’s the Internet, and nothing is obvious. So there: The title is tongue-in-cheek and I’m very well aware of <a href="http://meyerweb.com/eric/comment/chech.html">Eric’s classic essay against such titles</a>.</p>
Idea: Extending native DOM prototypes without collisions2015-04-20T00:00:00Zhttps://lea.verou.me/?p=2517<p>As I pointed out in <a href="http://lea.verou.me/2015/04/jquery-considered-harmful/">yesterday’s blog post</a>, one of the reasons why I don’t like using jQuery is its wrapper objects. For jQuery, this was a wise decision: Back in 2006 when it was first developed, IE releases had a pretty icky <strong>memory leak bug</strong> that could be easily triggered when one added properties to elements. Oh, and we also didn’t have access to element prototypes on IE back then, so we had to add these properties manually on every element. <a href="http://prototypejs.org/">Prototype.js</a> attempted to go that route and the result was such a mess that they decided to change their decision in Prototype 2.0 and go with wrapper objects too. There were even <a href="http://perfectionkills.com/whats-wrong-with-extending-the-dom/">long essays being written back then about how much of a monumentally bad idea it was to extend DOM elements</a>.</p>
<p>The first IE release that exposed element prototypes was IE8: We got access to Node.prototype, Element.prototype and a few more. Some were mutable, some were not. On IE9, we got the full bunch, including HTMLElement.prototype and its descendants, such as HTMLParagraphElement. The memory leak bugs were mitigated in IE8 and fixed in IE9. However, we still don’t extend native DOM elements, and for good reason: collisions are still a very real risk. No library wants to add a bunch of methods on elements, it’s just bad form. It’s like being invited in someone’s house and defecating all over the floor.</p>
<p><strong>But what if we could add methods to elements without the chance of collisions?</strong> (well, technically, by minimizing said chance). We could only add <strong>one property</strong> to Element.prototype, and then hang all our methods on that. E.g. if our library was called yolo and had two methods, foo() and bar(), calls to it would look like:</p>
<pre><code class="language-javascript">var element = document.querySelector(".someclass");
element.yolo.foo();
element.yolo.bar();
// or you can even chain, if you return the element in each of them!
element.yolo.foo().yolo.bar();
</code></pre>
<p>Sure, it’s more awkward than wrapper objects, but the benefit of using native DOM elements is worth it if you ask me. Of course, YMMV.</p>
<p><strong>It’s basically exactly the same thing we do with globals</strong>: We all know that adding tons of global variables is bad practice, so every library adds one global and hangs everything off of that.</p>
<p>However, if we try to implement something like this in the naïve way, we will find that it’s kind of hard to reference the element used from our namespaced functions:</p>
<pre><code class="language-javascript">Element.prototype.yolo = {
foo: function () {
console.log(this);
},
bar: function () { /* ... */ }
};
someElement.yolo.foo(); // Object {foo: function, bar: function}
</code></pre>
<p>What happened here? <code>this</code> inside any of these functions refers to the object that they are called on, not the element that object is hanging on! We need to be a bit more clever to get around this issue.</p>
<p>Keep in mind that <code>this</code> in the object inside <code>yolo</code> <em>would</em> have access to the element we’re trying to hang these methods off of. But we’re not running any code there, so we’re not taking advantage of that. If only we could get a reference to that object’s context! However, running a function (e.g. <code>element.yolo().foo()</code>) would spoil our nice API.</p>
<p>Wait a second. We can run code on properties, via ES5 accessors! We could do something like this:</p>
<pre><code class="language-javascript">Object.defineProperty(Element.prototype, "yolo", {
get: function () {
return {
element: this,
foo: function() {
console.log(this.element);
},
bar: function() { /* ... */ }
}
},
configurable: true,
writeable: false
});
someElement.yolo.foo(); // It works! (Logs our actual element)
</code></pre>
<p>This works, but there is a rather annoying issue here: We are <strong>generating this object and redefining our functions every single time this property is called.</strong> This is a rather bad idea for performance. Ideally, we want to <strong>generate this object once</strong>, and then return the generated object. We also don’t want every element to have its own completely separate instance of the functions we defined, we want to define these functions on a prototype, and use the wonderful JS inheritance for them, so that our library is also dynamically <strong>extensible</strong>. Luckily, there is a way to do all this too:</p>
<pre><code class="language-javascript">var Yolo = function(element) {
this.element = element;
};
Yolo.prototype = {
foo: function() {
console.log(this.element);
},
bar: function() { /* ... */ }
};
Object.defineProperty(Element.prototype, "yolo", {
get: function () {
Object.defineProperty(this, "yolo", {
value: new Yolo(this)
});
return this.yolo;
},
configurable: true,
writeable: false
});
someElement.yolo.foo(); // It works! (Logs our actual element)
// And it’s dynamically extensible too!
Yolo.prototype.baz = function(color) {
this.element.style.background = color;
};
someElement.yolo.baz("red") // Our element gets a red background
</code></pre>
<p>Note that in the above, <strong>the getter is only executed once</strong>. After that, it overwrites the <code>yolo</code> property with a static value: An instance of the <code>Yolo</code> object. Since we’re using <code>Object.defineProperty()</code> we also don’t run into the issue of breaking enumeration (<code>for..in</code> loops), since these properties have <code>enumerable: false</code> by default.</p>
<p>There is still the wart that these methods need to use <code>this.element</code> instead of <code>this</code>. We could fix this by wrapping them:</p>
<pre><code class="language-javascript">for (let method in Yolo.prototype) {
Yolo.prototype[method] = function(){
var callback = Yolo.prototype[method];
Yolo.prototype[method] = function () {
var ret = callback.apply(this.element, arguments);
// Return the element, for chainability!
return ret === undefined? this.element : ret;
}
}
}
</code></pre>
<p>However, now you can’t dynamically add methods to <code>Yolo.prototype</code> and have them automatically work like the native Yolo methods in <code>element.yolo</code>, so it kinda hurts extensibility (of course you could still add methods that use <code>this.element</code> and they would work).</p>
<p>Thoughts?</p>
Conical gradients, today!2015-06-18T00:00:00Zhttps://lea.verou.me/?p=2534<p><a href="https://lea.verou.me/2015/06/conical-gradients-today/images/Screen-Shot-2015-06-18-at-16.26.40-.png"><img src="https://lea.verou.me/2015/06/conical-gradients-today/images/Screen-Shot-2015-06-18-at-16.26.40-.png" alt="Screen Shot 2015-06-18 at 16.26.40" /></a>It’s no secret that I like conical gradients. In as early as 2011, I wrote <a href="http://lea.verou.me/specs/conical-gradient/">a draft for conical-gradient() in CSS</a>, that <a href="http://www.xanthir.com/blog/">Tab</a> later said helped him when he <a href="http://www.w3.org/TR/css4-images/#conic-gradients">added them in CSS Image Values Level 4</a> in 2012. However, almost <strong>three years later, no progress has been made</strong> in implementing them. Sure, the spec is still relatively incomplete, but that’s not the reason conical gradients have gotten no traction. <strong>Far more underspecified features have gotten experimental implementations in the past.</strong> The reason conical gradients are still unimplemented, is because very few developers know they exist, so browsers see no demand.</p>
<p>Another reason was that <a href="http://cairographics.org/">Cairo</a>, the graphics library used in Chrome and Firefox had no way of drawing a conical gradient. However, this changed a while ago, when <a href="http://libregraphicsworld.org/blog/entry/mesh-gradients-in-cairo-now-official">they supported mesh gradients</a>, of which conical gradients are a mere special case.</p>
<p>Recently, I was giving a talk on creating pie charts with CSS on a few conferences, and yet again, I was reminded of how useful conical gradients can be. While every CSS or SVG solution is several lines of code with varying levels of hackiness, conical gradients can give us a pie chart with a straightforward, DRY, one liner. For example, this is how to create a pie chart that shows 40% in gold and 60% in #f06:</p>
<pre><code>padding: 5em; /* size */
background: conic-gradient(gold 40%, #f06 0);
border-radius: 50%; /* make it round */
</code></pre>
<p><a href="https://lea.verou.me/2015/06/conical-gradients-today/images/Screen-Shot-2015-06-18-at-16.23.57-.png"><img src="https://lea.verou.me/2015/06/conical-gradients-today/images/Screen-Shot-2015-06-18-at-16.23.57-.png" alt="Screen Shot 2015-06-18 at 16.23.57" /></a> So, I decided to take matters in my own hands. I wrote <a href="https://projects.verou.me/conic-gradient/">a polyfill</a>, which I also used in my talk to demonstrate how awesome conical gradients can be and what cool things they can do. Today, during my <a href="http://cssconf.com/">CSSConf</a> talk, I released it publicly.</p>
<p>In addition, I mention to developers how important speaking up is for getting their favorite features implemented. <strong>Browsers prioritize which features to implement based on what developers ask for.</strong> It’s a pity that so few of us realize how much of a say we collectively have in this. This is more obvious with Microsoft and <a href="https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer">their Uservoice forum</a> where developers can vote on which features they want to see worked on, but pretty much every major browser works in a similar way. They monitor what developers request and what other browsers implement, and decide accordingly. The squeaky wheel will get the feature, so if you really want to see something implemented, <strong>speak up</strong>.</p>
<p>Since “speaking up” can be a bit vague (<em>“speak up where?”</em> I can hear you asking), I also filed bug reports with all major browsers, that you can also find <a href="https://projects.verou.me/conic-gradient/#ask">in the polyfill page</a>, so that you can comment or vote on them. That doesn’t mean that speaking up on blogs or social media is not useful though: That’s why browsers have devrel teams. The more noise we collectively make about the features we want, the more likely it is to be heard. However, the odds are higher if we all channel our voices to the venues browser developers follow and our voice is stronger and louder if we concentrate it in the same places instead of having many separate voices all over the place.</p>
<p>Also, I’m using the term “noise” here a bit figuratively. While it’s valuable to make it clear that we are interested in a certain feature, it’s even more useful to say <strong>why</strong>. Providing use cases will not only grab browsers’ attention more, but it will also convince other developers as well.</p>
<p>So go ahead, play with conic gradients, and if you agree with me that they are fucking awesome and we need them natively on the Web, <strong>make noise</strong>.</p>
<p><a href="https://projects.verou.me/conic-gradient/">conic-gradient() polyfill</a></p>
Spot the unsubscribe (link)!2015-07-14T00:00:00Zhttps://lea.verou.me/?p=2541<blockquote>
<p><a href="https://lea.verou.me/2015/07/spot-the-unsubscribe-link/images/Screen-Shot-2015-07-28-at-19.39.34-.png"><img src="https://lea.verou.me/2015/07/spot-the-unsubscribe-link/images/Screen-Shot-2015-07-28-at-19.39.34--300x136.png" alt="Screen Shot 2015-07-28 at 19.39.34" /></a>After getting fed up with too many “promotional” emails and newsletters with incredibly obscure unsubscribe links, I decided to make this tumblr to point out such examples of digital douchebaggery. This annoying dark pattern is so widespread that <a href="http://www.businessinsider.com/google-obvious-unsubscribe-link-email-2014-2">Google even added a feature to Gmail for making those unsubscribe links obvious</a>!</p>
<p>Unsubscribe links are crucial to promotional emails. They are not just another menu item. They are not something that should be hidden in a blurb of tiny low contrast text. Unsubscribe links should be immediately obvious to anyone looking for them. You want people to be reading your email because they’re interested, not because they can‘t find the way out. Otherwise you are the digital equivalent of those annoying door-to-door salesmen who just won’t go away.</p>
<p>— From my introductory post on <a href="http://spottheunsubscribe.tumblr.com/post/124094024596/o-hai">Spot the unsubscribe!</a></p>
</blockquote>
<p>On the spur of the moment, after yet another email newsletter with a hard to find Unsubscribe link, I decided to quickly put together a <a href="http://spottheunsubscribe.tumblr.com/">tumblog</a> about this UX pet peeve of mine, called <a href="http://spottheunsubscribe.tumblr.com/">Spot the Unsubscribe!</a>. In less than an hour, it was ready and had a few posts as well :)</p>
<p>Hopefully if this bothers others as well, there will be <a href="http://spottheunsubscribe.tumblr.com/submit">submissions</a>. Otherwise, new posts will be rather infrequent.</p>
Stretchy: Form element autosizing, the way it should be2015-07-26T00:00:00Zhttps://lea.verou.me/?p=2544<p><a href="https://lea.verou.me/2015/07/stretchy-form-element-autosizing-the-way-it-should-be/images/Screen-Shot-2015-07-25-at-18.40.13-.png"><img src="https://lea.verou.me/2015/07/stretchy-form-element-autosizing-the-way-it-should-be/images/Screen-Shot-2015-07-25-at-18.40.13--300x204.png" alt="Screen Shot 2015-07-25 at 18.40.13" /></a>As you might be aware, <a href="http://lea.verou.me/2014/02/im-going-to-mit/">these days a good chunk of my time is spent working on research, at MIT</a>. Although it’s still too early to talk about my research project, I can say that it’s related to the Web and it will be open source, both of which are pretty awesome (getting paid to work on cool open source stuff is the dream, right?).</p>
<p>The one thing I <em>can</em> mention about my project is that it involves a lot of editing of Web content. And since contentEditable is a mess, as you all know, I decided to use form controls styled like the content being edited. This meant that I needed a good script for form control autosizing, one that worked on multiple types of form controls (inputs, textareas, even select menus). In addition, I needed the script to smoothly work for newly added controls, without me having to couple the rest of my code with it and call API methods or fire custom events every time new controls were added anywhere. A quick look at the existing options quickly made it obvious that I had to write my own.</p>
<p>After writing it, I realized this could be released entirely separately as it was a standalone utility. So <a href="https://projects.verou.me/stretchy/">Stretchy</a> was born :) I made a quick page for it, fixed a few cross-browser bugs that needed fixing anyway, put it up on Github and here it is!</p>
<p><a href="https://projects.verou.me/stretchy">Enjoy!</a></p>
<p>PS: You can also use it as a bookmarklet, to autosize form controls on an existing page, if a form is bothering you with its poor usability. You will find it in the footer.</p>
On the blindness of blind reviews2015-08-13T00:00:00Zhttps://lea.verou.me/?p=2554<p>Over the last couple of years, blind reviews have been popularized as the ultimate method for fair talk selection in industry conferences. While I don’t really submit proposals myself, I have served several times on the other side of the process, doing speaker selection in conference committees, and the more data points I collect, the more convinced I become that the blind selection process is fundamentally flawed.</p>
<p>Blind reviews come from the world of academia. However, in academic conferences, you do not judge a talk by a 1-2 paragraph abstract, but by a 10+ page paper, so there’s way more to judge by. In addition, in academia the content of the research matters infinitely more than the quality of a talk. In industry conferences, selection committees in blind reviews have both <strong>way less data to use</strong>, and a <strong>much harder task</strong>, as they need to balance several factors (content, speaker skill, talk quality etc). It’s no surprise that the results end up being even more of a gamble.</p>
<p><strong>Blind reviews result in conservative talk selection.</strong> More often than not, I remember me and my fellow committee members saying “Damn, this talk could be great with the right presenter, but that’s rare” and giving it a poor or average score. <strong>Few topics can make good talks regardless of the presenters.</strong> Therefore, when there is little information on the speaker in the initial selection round, talk selection ends up being conservative, rejecting more challenging topics that need a skilled speaker to shine and sticking to safer choices.</p>
<p>One of my most successful talks ever was “The humble border-radius” which was shortlisted for a .net award for Conference Talk of The Year 2014. <strong>It would never have passed any blind review.</strong> There is no committee in their right mind that would have accepted a 45 minute talk about …border-radius. The conferences I presented it at invited me as a speaker, carte blanche, and trusted me to present on whatever I felt like. Judging by the reviews, they were not disappointed.</p>
<p>In addition, all too many times I’ve seen great speakers get poor scores in blind reviews, not because their talks were not good, but because <strong>writing good abstracts is an entirely separate skill</strong>. Blind reviews remove anything that could cause bias, but they do so by striping all personality away from a proposal. In addition, a good abstract for a blind review is not necessarily a good abstract in general. For example, blind reviews penalize more mysterious/teasy abstracts and tend to be skewed towards overly detailed ones, since it’s the only data the committee gets for these talks (bonus points here for CfS that have a separate field for more details to conf organizers).</p>
<p><strong>“But what about newcomers to the conference circuit? What about bias elimination?”</strong> one might ask. Both very valid concerns. I’m not saying any kind of anonymization is a bad idea. I’m saying that in their present form in industry conferences, blind reviews are flawed. For example, an initial round of blind reviews to pick good talks, without rejecting any at that stage, would probably solve these issues, without suffering from the flaws mentioned above.</p>
<p><strong>Disclaimer:</strong> I do recognize that most people in these committees are doing their best to select fairly, and putting many hours of (usually volunteer) work in it. I’m not criticizing them, I’m criticizing the process. And yes, I recognize that it’s a process that has come out of very good intentions (eliminating bias). However, <strong>good intentions are not a guarantee for infallibility</strong>.</p>
Copying object properties, the robust way2015-08-16T00:00:00Zhttps://lea.verou.me/?p=2558<p>If, like me, you try to <a href="http://lea.verou.me/2015/04/jquery-considered-harmful/">avoid using heavy libraries when not needed</a>, you must have definitely written a helper to copy properties from one object to another at some point. It’s needed so often that it’s just silly to write the same loops over and over again.</p>
<p>These days, most of my time is spent working on <a href="http://lea.verou.me/2014/02/im-going-to-mit/">my research project at MIT</a>, which I will hopefully reveal later this year. In that, I’m using a lightweight homegrown helper library, which I might release separately at some point as I think it has potential in its own right, for a number of reasons.</p>
<p>Of course, it needed to have a simple <code>extend()</code> method as well, to copy properties from one object to another. Let’s assume for the purposes of this article that we’re talking about shallow copying, that overwrites are allowed, and let’s omit <code>hasOwnProperty()</code> checks to make code easier to read.</p>
<p>It’s a simple task, right? Our first attempt might look like this:</p>
<pre><code class="language-javascript">$.extend = function (to, from) {
for (var property in from) {
to[property] = from[property];
}
return to;
}
</code></pre>
<p>This works fine, until you try it on objects with accessors or other types of properties defined via <code>Object.defineProperty()</code> or <code>get</code>/<code>set</code> keywords. What do you do then? Our next iteration could look like this:</p>
<pre><code class="language-javascript">$.extend = function (to, from) {
for (var property in from) {
Object.defineProperty(to, property, Object.getOwnPropertyDescriptor(from, property));
}
return to;
}
</code></pre>
<p>This works much better, until it fails, and it can fail pretty epically. Try this:</p>
<pre><code class="language-javascript">$.extend(document.body.style, {
backgroundColor: "red"
});
</code></pre>
<p>Both in Chrome and Firefox, the results are super weird. Even though reading <code>document.body.style.backgroundColor</code> will return <code>"red"</code>, no style will have actually been applied. In Firefox it even destroyed the native setter entirely and any future attempts to set <code>document.body.style.backgroundColor</code> in the console did absolutely nothing.</p>
<p>In contrast, the previous naïve approach worked fine for this. It’s clear that we need to somehow combine the two approaches, using Object.defineProperty() only when actually needed. But when is it actually not needed?</p>
<p>One obvious case is if the descriptor is <code>undefined</code> (such as with some native properties). Also, in simple properties, such as those in our object literal, the descriptor will be of the form <code>{value: somevalue, writable: true, enumerable: true, configurable: true}</code>. So, the next obvious step would be:</p>
<pre><code class="language-javascript">$.extend = function (to, from) {
var descriptor = Object.getOwnPropertyDescriptor(from, property);
if (descriptor && (!descriptor.writable || !descriptor.configurable || !descriptor.enumerable || descriptor.get || descriptor.set)) {
Object.defineProperty(to, property, descriptor);
}
else {
to[property] = from[property];
}
}
</code></pre>
<p>This works perfectly, but is a little clumsy. I’ve currently left it at that, but any suggestions for making it more elegant are welcome :)</p>
<p>FWIW, I looked at <a href="http://james.padolsey.com/jquery/#v=git&fn=jQuery.extend">jQuery’s implementation of jQuery.extend()</a> after this, and it seems it doesn’t even handle accessors at all, unless I missed something. Time for a pull request, perhaps…</p>
<p><strong>Edit:</strong> As MaxArt pointed out in the comments, there is a similar native method in ES6, <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign"><code>Object.assign()</code></a>. However, it does not deal with copying accessors, so does not deal with this problem either.</p>
Introducing Bliss: A 3KB library for happier Vanilla JS2015-12-04T00:00:00Zhttps://lea.verou.me/?p=2570<p><a href="http://blissfuljs.com/"><img src="https://lea.verou.me/2015/12/introducing-bliss-a-3kb-library-for-happier-vanilla-js/images/Screen-Shot-2015-12-04-at-16.59.39-300x127.png" alt="Screen Shot 2015-12-04 at 16.59.39" /></a>Anyone who follows this blog, my twitter, or my work probably is aware that <a href="http://lea.verou.me/2015/04/jquery-considered-harmful/">I’m not a huge fan of big libraries</a>. I think wrapper objects are messy, and big libraries are overkill for smaller projects. On large projects, one uses frameworks like React or Angular anyway, not libraries.</p>
<p>Anyone who writes <a href="http://vanilla-js.com/">Vanilla JS</a> on a daily basis probably is aware that it can sometimes be, ahem, somewhat unpleasant to work with. Sure, the situation is orders of magnitude better than it was when I started. Back then, IE6 was the dominant browser and you needed a helper function to even add event listeners to an element (remember element.attachEvent?) or to get elements by a class!</p>
<p><a href="https://lea.verou.me/2015/12/introducing-bliss-a-3kb-library-for-happier-vanilla-js/images/jasset-datepicker.png"><img src="https://lea.verou.me/2015/12/introducing-bliss-a-3kb-library-for-happier-vanilla-js/images/jasset-datepicker-300x211.png" alt="jasset-datepicker" title="Ah, the memories!" /></a></p>
<p><strong>Fun fact:</strong> I learned JavaScript back then by writing my own library, called <em>jAsset</em>. I had not heard of jQuery when I started it in 2007, so I had even coded my own selector engine! (Anyone remember <em>slickspeed</em>?) jAssset had plenty of nice helper functions, its own UI library and a cool logo. I had even started to make a website for its UI components, seen on the right.</p>
<p><a href="https://shipitsquirrel.github.io/"><img src="http://shipitsquirrel.github.io/images/ship%20it%20squirrel.png" alt="" /></a>Sadly, <em>jAsset</em> died the sad inevitable death of all unreleased projects: Without external feedback, I had nobody to hold me back from adding to its API every time I personally needed a helper function. And adding, and adding, and adding… Until it became 5000+ loc long and its benefit of being lightweight or comprehensible had completely vanished. It collapsed under its own weight before it even saw the light of day. I abandoned it and went through a few years of using jQuery as my preferred helper library. Eventually, my <a href="http://lea.verou.me/2015/04/jquery-considered-harmful/">distaste for wrapper objects</a>, the constantly improving browser support for new APIs that made Vanilla JS more palatable, and the decline of overly conspicuous browser bugs led me to give it up.</p>
<p>It was refreshing, and educational, but soon I came to realize that while Vanilla JS is orders of magnitude better than it was when I started, certain APIs are still quite unwieldy, which can be annoying if you use them often. For example, the Vanilla JS for creating an element, with other elements inside it, events and inline styles is so commonly needed, but also so verbose and <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">WET</a>, it can make one suicidal.</p>
<p>However, Vanilla JS does not mean “use no abstractions”. Programming is all about abstractions! The Vanilla JS movement, is about favoring speed, smaller abstractions and understanding of the Web Platform, over big libraries that we treat as a black box. It’s about using libraries to save time, not to skip learning.</p>
<p>So, I used my own tiny helpers, on every project. They were small and easy to understand, instead of several KB of code aiming to fix browser bugs I will likely never encounter and let me create complex nested DOM structures with a single JSON-like object. Over time, their API solidified and improved. On larger projects it was a separate file which I had tentatively codenamed <em>Utopia</em> (due to the lack of browser bug fixes and optimistic use of modern APIs). On smaller ones just a few helper methods (I could not live without at least my tiny 2 sloc $() and $$() helpers!). Here is a sample from my open source repos:</p>
<ul>
<li><a href="https://github.com/LeaVerou/dabblet/blob/master/code/utopia.js">dabblet.com/utopia.js</a></li>
<li><a href="https://github.com/LeaVerou/regexplained/blob/gh-pages/utopia.js">regexplained/utopia.js</a></li>
<li><a href="https://github.com/LeaVerou/dpi/blob/gh-pages/utopia.js">dpi.lv/utopia.js</a></li>
<li><a href="https://github.com/LeaVerou/css3test/blob/gh-pages/utopia.js">css3test.com/utopia.js</a></li>
<li><a href="https://github.com/LeaVerou/awesomplete/blob/gh-pages/awesomplete.js#L296-L351">awesomplete.js</a></li>
<li>$() and/or $$() helpers in:
<ul>
<li><a href="https://github.com/LeaVerou/prefixfree/blob/gh-pages/prefixfree.js#L167-L169">prefixfree</a></li>
<li><a href="https://github.com/LeaVerou/csss/blob/gh-pages/slideshow.js#L10-L11">CSSS</a></li>
<li><a href="https://github.com/LeaVerou/animatable/blob/gh-pages/index.js#L1-L2">animatable</a></li>
<li><a href="https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/contrast-ratio.js#L1-L7">contrast-ratio</a></li>
<li><a href="https://github.com/LeaVerou/cubic-bezier/blob/gh-pages/environment.js#L27-L28">cubic-bezier.com</a></li>
<li><a href="https://github.com/LeaVerou/whathecolor/blob/gh-pages/whathecolor.js#L1-L7">whathecolor</a></li>
<li><a href="https://github.com/LeaVerou/corner-shape/blob/gh-pages/corner-shape.js#L1-L2">corner-shape</a></li>
<li><a href="https://github.com/LeaVerou/css-colors/blob/gh-pages/colors.js#L1-L2">css-colors</a></li>
</ul>
</li>
</ul>
<p>Notice any recurring themes there? :)</p>
<p>I never mentioned <em>Utopia.js</em> anywhere, besides silently including it in my projects, so it went largely unnoticed. Sometimes people would look at it, ask me to release it, I’d promise them I would and then nothing. A few years ago, someone noticed it, liked it and <a href="http://davidhiggins.me/utopia/">documented it</a> a bit (site is down now it seems). However, it was largely my little secret, hidden in public view.</p>
<p>For the past half year, I’ve been working hard on my research project at MIT. It’s pretty awesome and is aimed at helping people who know HTML/ CSS but <strong>not</strong> JS, achieve more with Web technologies (and that’s all I can say for now). It’s also written in JS, so I used <em>Utopia</em> as a helper library, naturally. <em>Utopia</em> evolved even more with this project, got renamed to <em>Bliss</em> and got chainability via <a href="http://lea.verou.me/2015/04/idea-extending-native-dom-prototypes-without-collisions/">my idea about extending DOM prototypes without collisions</a> (can be disabled and the property name is customizable).</p>
<p>All this worked fine while I was the only person working on the project. Thankfully, I might get some help soon, and it might be rather inexperienced (the academia equivalent of interns). Help is very welcome, but it did raise the question: How will these people, who likely only know jQuery, work on the project? [1]</p>
<p>The answer was that the time has come to polish, document and release <a href="http://blissfuljs.com/">Bliss</a> to the world. My plan was to spend a weekend documenting it, but it ended up being a little over a week on and off, when procrastinating from other tasks I had to do. However, I’m very proud of the resulting docs, so much that I gifted myself <a href="http://blissfuljs.com/">a domain</a> for it. They are fairly extensive (though some functions still need work) and has two things I always missed in other API docs:</p>
<ul>
<li>Recommendations about what Vanilla JS to use instead when appropriate, instead of guiding people into using library methods even when Vanilla JS would have been perfectly sufficient.</li>
<li>A “Show Implementation” button showing the implementation, so you can both learn, and judge whether it’s needed or not, instead of assuming that you should use it over Vanilla JS because it has magic pixie dust. This way, the docs also serve as a source viewer!</li>
</ul>
<p>So, enjoy <a href="http://blissfuljs.com/">Bliss</a>. The helper library for people who don’t like helper libraries. :) In a way, it feels that a journey of 8 years, finally ends today. I hope the result makes you blissful too.</p>
<p><a href="http://blissfuljs.com/">blissfuljs.com</a></p>
<p>Oh, and don’t forget to follow <a href="http://twitter.com/blissfuljs">@blissfuljs</a> on twitter!</p>
<p>[1]: Academia is often a little behind tech-wise, so <strong>everyone</strong> uses jQuery here — hardly any exceptions. Even though browser support doesn’t usually even matter to research projects!</p>
My positive experience as a woman in tech2015-12-17T00:00:00Zhttps://lea.verou.me/?p=2590<p>Women speaking up about the sexism they have experienced in tech is great for raising awareness about the issues. However, <strong>when no positive stories get out, the overall picture painted is bleak, which could scare even more women away</strong>.</p>
<p>Lucky for me, <a href="http://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/">I fell in love with programming a decade before I even heard there is a sexism problem in tech</a>. Had I read about it before, I might have decided to go for some other profession. Who wants to be fighting an uphill battle all her life?</p>
<p>Thankfully, my experience has been quite different. Being in this industry has brought me nothing but happiness. Yes, there are several women who have had terrible experiences, and I’m in no way discounting them. They may even be the majority, though I am not aware of any statistics. However, there is also the other side. Those of us who have had incredibly positive experiences, and have always been treated with nothing but respect. That side’s stories need to be heard too, not silenced out of fear that we will become complacent and stop trying for more equality. Stories like mine should become the norm, not the exception.</p>
<p>I’ve had a number of different roles in tech over the course of my life. I’ve been a student, a speaker & author, I’ve worked at <a href="http://w3.org/">W3C</a>, I’ve started & maintain several successful open source projects and I’m <a href="http://lea.verou.me/2014/02/im-going-to-mit/">currently dabbling in Computer Science research</a>. In none of these roles did I ever feel I was unfairly treated due to my gender. That is not because I’m oblivious to sexism. I tend to be very sensitive to seeing it, and I often notice even the smallest acts of sexism (“death by a thousand paper cuts”). I see a lot of sexism in society overall. However, inside this industry, my gender never seemed to matter much, except perhaps in positive ways.</p>
<p>On <a href="http://github.com/leaverou">my open source repos</a>, I have several contributors, the overwhelming majority of which, is male. I’ve never felt less respected due to my gender. I’ve never felt that my work was taken less seriously than male OSS developers. I’ve never felt my contributors would not listen to me. I’ve never felt my work was unfairly scrutinized. Even when I didn’t know something, or introduced a horrible bug, I’ve never been insulted or berated. The community has been nothing but friendly, helpful and respectful. If anything, I’ve sometimes wondered if my gender is the reason I hardly ever get any shit!</p>
<p>On stage, I’ve never gotten any negative reactions. My talks always get excellent reviews, which have nothing to do with me being female. There is sometimes the odd complimentary tweet about my looks, but that’s not only exceedingly rare, but also always combined with a compliment about the actual talk content. My gender only affected my internal motivation: I often felt I <strong>had</strong> to be good, otherwise I would be painting all female tech speakers in a negative light. But other people are not at fault for my own stereotype threat.</p>
<p>My book, <a href="http://www.amazon.com/CSS-Secrets-Solutions-Everyday-Problems/dp/1449372635/ref=cm_cr_pr_product_top?ie=UTF8">CSS Secrets</a>, has been as successful as an advanced CSS book could possibly aspire to be and got to an average of 5 stars on Amazon only a few months after its release. It’s steadily the 5th bestseller on CSS and was No 1 for a while shortly after publication. My gender did not seem to negatively affect any of that, even though there’s a picture of me in the french flap so there are no doubts about me being female (as if the name Lea wasn’t enough of a hint).</p>
<p>As a student, I’ve never felt unfairly treated due to my gender by any of my professors, even the ones in Greece, a country that is not particularly famous for its gender equal society, to put it mildly.</p>
<p>As a new researcher, I have no experience with publishing papers yet, so I cannot share any experiences on that. However, I’ve been treated with nothing but respect by both <a href="https://en.wikipedia.org/wiki/David_Karger">my advisor</a> and colleagues. My opinion is always heard and valued and even when people don’t agree, I can debate it as long and as intensely as I want, without being seen as aggressive or “bossy”.</p>
<p>I’ve worked at <a href="http://w3.org/">W3C</a> and still participate as an Invited Expert in the CSS Working Group. In neither of these roles did my gender seem to matter in any way. I’ve always felt that my expertise and skillset were valued and my opinions heard. In fact, the most well-respected member of the CSS WG, is the only other woman in it: <a href="http://fantasai.inkedblade.net/">fantasai</a>.</p>
<p>Lastly, In all my years as a working professional, I’ve always negotiated any kind of remuneration, often hard. I’ve never lost an opportunity because of it, or been treated with negativity afterwards.</p>
<p>On the flip side, sexism today is rarely overt. Given that hardly anybody over ten will flat out admit they think women are inferior (even to themselves), it’s often hard to tell when a certain behavior stems from sexist beliefs. If someone is a douchebag to you, are they doing it because you’re a woman, or because they’re douchebags? If someone is criticizing your work, are they doing it because they genuinely found something to criticize or because they’re negatively predisposed due to your gender? It’s impossible to know, especially since <strong>they don’t know either</strong>! If you confront them on their sexism, they will deny all of it, and truly believe it. <strong>It takes a lot of introspection to see one’s internalized stereotypes.</strong> Therefore, a lot of the time, you cannot be sure if you have experienced sexist behavior, and there is no way to find out for sure, since the perpetrator doesn’t know either. There are many false positives and false negatives there.</p>
<p>Perhaps I don’t feel I have experienced much sexism because I prefer to err on the side of false negatives. Paraphrasing <a href="https://en.wikipedia.org/wiki/Blackstone%27s_formulation">Blackstone</a>, I would rather not call out sexist behavior ten times, than wrongly accuse someone of it once. It might also have to do with my personality: I’m generally confident and can be very assertive. When somebody is being a jerk to me, I will not curl in a ball and question my life choices, I will reply to them in the same tone. However, those two alone cannot make the difference between a pit rampant with sexism and an egalitarian paradise. I think a lot of it is that we have genuinely made progress, and we should celebrate it with more women coming out with their positive experiences (it cannot just be me, right?).</p>
<p>Ironically, one of the very few times I have experienced any sexism in the industry was when a dude was trying to be nice to me. I was in a speaker room at a conference in Las Vegas, frantically working on my slides, not participating in any of the conversations around me. At some point, one of the guys said “fuck” in a conversation, then turned and apologized to me. Irritated about the sudden interruption, I lifted my head and looked around. <strong>I noticed for the first time that day that I was the only woman in the room.</strong> <strong>His effort to be courteous made me feel that I was different, the odd one out</strong>, the one we must be careful around and treat like a fragile flower. To this day, I regret being too startled to reply <em>“Eh, I don’t give a fuck”</em>.</p>
Introducing Multirange: A tiny polyfill for HTML5.1 two-handle sliders2016-05-31T00:00:00Zhttps://lea.verou.me/?p=2621<p><a href="https://lea.verou.me/2016/05/introducing-multirange-a-tiny-polyfill-for-html5-two-handle-sliders/images/multirange.png"><img src="https://lea.verou.me/2016/05/introducing-multirange-a-tiny-polyfill-for-html5-two-handle-sliders/images/multirange-300x206.png" alt="multirange" /></a>As part of my preparation for <a href="http://cssday.nl/2016/programme#lea-verou">my talk at CSSDay HTML Special</a>, I was perusing the most recent HTML specs (<a href="https://html.spec.whatwg.org/multipage/">WHATWG Living Standard</a>, <a href="https://www.w3.org/TR/html51/">W3C HTML 5.1</a>) to see what undiscovered gems lay there. It turns out that HTML sliders have a lot of cool features specced that aren’t very well implemented:</p>
<ul>
<li>Ticks that snap via the <code>list</code> attribute and the <code><datalist></code> element. This is fairly decently implemented, except labelled ticks, which is not supported anywhere.</li>
<li>Vertical sliders when height > width, implemented nowhere (instead, browsers employ proprietary ways for making sliders vertical: An <code>orient=vertical</code> attribute in Gecko, <code>-webkit-appearance: slider-vertical;</code> in WebKit/Blink and <code>writing-mode: bt-lr;</code> in IE/Edge). Good ol’ rotate transforms work too, but have the usual problems, such as layout not being affected by the transform.</li>
<li>Two-handle sliders for ranges, via the <code>multiple</code> attribute.</li>
</ul>
<p>I made a quick <a href="http://dabblet.com/gist/0b79583e6e9c4e5e52aec5d682ac71d2">testcase</a> for all three, and to my disappointment (but not to my surprise), support was extremely poor. I was most excited about the last one, since I’ve been wanting range sliders in HTML for a long time. Sadly, there are no implementations. But hey, what if I could create a polyfill by cleverly overlaying two sliders? Would it be possible? I started <a href="http://jsbin.com/risiki/edit?html,css,js,output">experimenting in JSBin</a> last night, just for the lolz, then soon realized this could actually work and <a href="https://github.com/leaverou/multirange">started a GitHub repo</a>. Since CSS variables are now supported almost everywhere, I’ve had a lot of fun using them. Sure, I could get broader support without them, but the code is much simpler, more elegant and customizable now. I also originally started with a <a href="http://blissfuljs.com/">Bliss</a> dependency, but realized it wasn’t worth it for such a tiny script.</p>
<p>So, enjoy, and contribute!</p>
<p><a href="https://projects.verou.me/multirange/">Multirange</a></p>
Markapp: A list of HTML libraries2016-08-26T00:00:00Zhttps://lea.verou.me/?p=2649<p><a href="https://lea.verou.me/2016/08/markapp-a-list-of-html-libraries/images/Screen-Shot-2016-08-26-at-17.09.24.png"><img src="https://lea.verou.me/2016/08/markapp-a-list-of-html-libraries/images/Screen-Shot-2016-08-26-at-17.09.24-300x234.png" alt="Screen Shot 2016-08-26 at 17.09.24" /></a>I have often lamented how many JavaScript developers don’t realize that a large percentage of HTML & CSS authors are not comfortable writing JS, and struggle to use their libraries.</p>
<p>To encourage libraries with HTML APIs, i.e. libraries that can be used without writing a line of JS, I made a website to list and promote them: <a href="http://markapp.io/">markapp.io</a>. The list is currently quite short, so I’m counting on you to <a href="https://github.com/LeaVerou/markapp">expand it</a>. Seen any libraries with good HTML APIs? Add them!</p>
Autoprefixing, with CSS variables!2016-09-07T00:00:00Zhttps://lea.verou.me/?p=2655<p>Recently, when I was making the minisite for <a href="http://markapp.io/">markapp.io</a>, I realized a neat trick one can do with CSS variables, precisely due to their dynamic nature. Let’s say you want to use a property that has multiple versions: an unprefixed one and one or more prefixed ones. In this example we are going to use <code>clip-path</code>, which <a href="http://caniuse.com/#feat=css-clip-path">currently</a> needs both an unprefixed version and a <code>-webkit-</code> prefixed one, however the technique works for any property and any number of prefixes or different property names, as long as the value is the same across all variations of the property name.</p>
<p>The first part is to define a <code>--clip-path</code> property on every element with a value of initial. This prevents the property from being inherited every time it’s used, and since the <code>*</code> has zero specificity, any declaration that uses <code>--clip-path</code> can override it. Then you define all variations of the property name with <code>var(--clip-path)</code> as their value:</p>
<pre><code>* {
--clip-path: initial;
-webkit-clip-path: var(--clip-path);
clip-path: var(--clip-path);
}
</code></pre>
<p>Then, every time we need clip-path, we use --clip-path instead and it just works:</p>
<pre><code>header {
--clip-path: polygon(0% 0%, 100% 0%, 100% calc(100% - 2.5em), 0% 100%);
}
</code></pre>
<p>Even <code>!important</code> should work, because <a href="https://www.w3.org/TR/css-variables/#syntax">it affects the cascading of CSS variables</a>. Furthermore, if for some reason you want to explicitly set <code>-webkit-clip-path</code>, you can do that too, again because * has zero specificity. The main downside to this is that it limits browser support to the intersection of the support for the feature you are using and support for CSS Variables. However, <a href="http://caniuse.com/#feat=css-variables">all browsers except Edge support CSS variables</a>, and <a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/status/csscustompropertiesakacssvariables/">Edge is working on it</a>. I can’t see any other downsides to it (except having to use a different property name obvs), but if you do, let me know in the comments!</p>
<p>I think there’s still a lot to be discovered about cool uses of CSS variables. I wonder if there exists a variation of this technique to produce custom longhands, e.g. breaking <code>box-shadow</code> into <code>--box-shadow-x</code>, <code>--box-shadow-y</code> etc, but I can’t think of anything yet. Can you? ;)</p>
URL rewriting with Github Pages2016-11-26T00:00:00Zhttps://lea.verou.me/?p=2668<p><a href="https://lea.verou.me/2016/11/url-rewriting-with-github-pages/images/redirect.png"><img src="https://lea.verou.me/2016/11/url-rewriting-with-github-pages/images/redirect-300x167.png" alt="redirect" /></a>I adore <a href="https://pages.github.com/">Github Pages</a>. I use them for everything I can, and try to avoid server-side code like the plague, exactly so that I can use them. The convenience of pushing to a repo and having the changes immediately reflected on the website with no commit hooks or any additional setup, is awesome. The free price tag is even more awesome. So, when the time came to publish <a href="https://www.amazon.com/CSS-Secrets-Solutions-Everyday-Problems/dp/1449372635/">my book</a>, naturally, I wanted the companion website to be on Github Pages.</p>
<p>There was only one small problem: I wanted nice URLs, like <a href="http://play.csssecrets.io/pie-animated">http://play.csssecrets.io/pie-animated</a>, which would redirect to demos on <a href="http://dabblet.com/">dabblet.com</a>. Any sane person would have likely bitten the bullet and used some kind of server-side language. However, I’m not a particularly sane person :D</p>
<p>Turns out <a href="https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/">Github uses some URL rewriting of its own on Github Pages</a>: If you provide a 404.html, any URL that doesn’t exist will be handled by that. Wait a second, is that basically how we do nice URLs on the server anyway? We can do the same in Github Pages, by just running JS inside 404.html!</p>
<p>So, I created <a href="https://github.com/LeaVerou/play.csssecrets.io/blob/gh-pages/secrets.json">a JSON file</a> with all demo ids and their dabblet URLs, a <a href="https://github.com/LeaVerou/play.csssecrets.io/blob/gh-pages/404.html">404.html</a> that shows either a redirection or an error (JS decides which one) and <a href="https://github.com/LeaVerou/play.csssecrets.io/blob/gh-pages/redirect.js">a tiny bit of Vanilla JS</a> that reads the current URL, fetches the JSON file, and redirects to the right dabblet. Here it is, without the helpers:</p>
<pre><code class="language-js">(function(){
document.body.className = 'redirecting';
var slug = location.pathname.slice(1);
xhr({
src: 'secrets.json',
onsuccess: function () {
var slugs = JSON.parse(this.responseText);
var hash = slugs[slug];
if (hash) {
// Redirect
var url = hash.indexOf('http') == 0? hash : 'http://dabblet.com/gist/' + hash;
$('section.redirecting > p').innerHTML = 'Redirecting to <a href="' + url + '">' + url + '</a>…';
location.href = url;
}
else {
document.body.className = 'error not-found';
}
},
onerror: function () {
document.body.className = 'error json';
}
});
})();
</code></pre>
<p>That’s all! You can imagine using the same trick to redirect to other HTML pages in the same Github Pages site, have proper URLs for a single page site, and all sorts of things! Is it a hack? Of course. But when did that ever stop us? :D</p>
Resolve Promises externally with this one weird trick2016-12-14T00:00:00Zhttps://lea.verou.me/?p=2675<p>Those of us who use promises heavily, have often wished there was a <code>Promise.prototype.resolve()</code> method, that would force an existing Promise to resolve. However, for architectural reasons (throw safety), there is no such thing and probably never will be. Therefore, a Promise can only resolve or reject by calling the respective methods in its constructor:</p>
<pre><code>var promise = new Promise((resolve, reject) => {
if (something) {
resolve();
}
else {
reject();
}
});
</code></pre>
<p>However, often it is not desirable to put your entire code inside a Promise constructor so you could resolve or reject it at any point. In my latest case today, I wanted a Promise that resolved when a tree was created, so that third-party components could defer code execution until the tree was ready. However, given that plugins could be running on any hook, that meant wrapping a ton of code with the Promise constructor, which was obviously a no-go. I had come across this problem before and usually gave up and created a Promise around all the necessary code. However, this time my aversion to what this would produce got me to think even harder. What could I do to call <code>resolve()</code> asynchronously from outside the Promise?</p>
<p>A custom event? Nah, too slow for my purposes, why involve the DOM when it’s not needed?</p>
<p>Another Promise? Nah, that just transfers the problem.</p>
<p>An setInterval to repeatedly check if the tree is created? OMG, I can’t believe you just thought that Lea, ewwww, gross!</p>
<p>Getters and setters? Hmmm, maybe that could work! If the setter is inside the Promise constructor, then I can resolve the Promise by just setting a property!</p>
<p>My first iteration looked like this:</p>
<pre><code>this.treeBuilt = new Promise((resolve, reject) => {
Object.defineProperty(this, "_treeBuilt", {
set: value => {
if (value) {
resolve();
}
}
});
});
// Many, many lines below…
this._treeBuilt = true;
</code></pre>
<p>However, it really bothered me that I had to define 2 properties when I only needed one. I could of course do some cleanup and delete them after the promise is resolved, but the fact that at some point in time these useless properties existed will still haunt me, and I’m sure the more OCD-prone of you know exactly what I mean. Can I do it with just one property? Turns out I can!</p>
<p>The main idea is realizing that the getter and the setter could be doing completely unrelated tasks. In this case, setting the property would resolve the promise and reading its value would return the promise:</p>
<pre><code>var setter;
var promise = new Promise((resolve, reject) => {
setter = value => {
if (value) {
resolve();
}
};
});
Object.defineProperty(this, "treeBuilt", {
set: setter,
get: () => promise
});
// Many, many lines below…
this.treeBuilt = true;
</code></pre>
<p>For better performance, once the promise is resolved you could even delete the dynamic property and replace it with a normal property that just points to the promise, but be careful because in that case, any future attempts to resolve the promise by setting the property will make you lose your reference to it!</p>
<p>I still think the code looks a bit ugly, so if you can think a more elegant solution, I’m all ears (well, eyes really)!</p>
<p><strong>Update:</strong> <a href="https://twitter.com/joseph_silber/status/809176159858655234">Joseph Silber gave an interesting solution on twitter</a>:</p>
<pre><code>function defer() {
var deferred = {
promise: null,
resolve: null,
reject: null
};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
this.treeBuilt = defer();
// Many, many lines below…
this.treeBuilt.resolve();
</code></pre>
<p>I love that this is reusable, and calling <code>resolve()</code> makes a lot more sense than setting something to true. However, I didn’t like that it involved a separate object (deferred) and that people using the treeBuilt property would not be able to call .then() directly on it, so I simplified it a bit to only use one Promise object:</p>
<pre><code>function defer() {
var res, rej;
var promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
promise.resolve = res;
promise.reject = rej;
return promise;
}
this.treeBuilt = defer();
// Many, many lines below…
this.treeBuilt.resolve();
</code></pre>
<p>Finally, something I like!</p>
Duoload: Simplest website load comparison tool, ever2017-02-03T00:00:00Zhttps://lea.verou.me/?p=2689<p><a href="https://lea.verou.me/2017/02/duoload-simplest-website-load-comparison-tool-ever/images/Screen-Shot-2017-02-02-at-23.49.02.png"><img src="https://lea.verou.me/2017/02/duoload-simplest-website-load-comparison-tool-ever/images/Screen-Shot-2017-02-02-at-23.49.02-300x190.png" alt="" /></a>Today I needed a quick tool to compare the loading progression (not just loading time, but also incremental rendering) of two websites, one remote and one in my localhost. Just have them side by side and see how they load relative to each other. Maybe even record the result on video and study it afterwards. That’s all. No special features, no analysis, no stats.</p>
<p>So I did what I always do when I need help finding a tool, I asked Twitter:</p>
<p><a href="https://twitter.com/LeaVerou/status/827327249305178113">https://twitter.com/LeaVerou/status/827327249305178113</a></p>
<p>Most suggested complicated tools, some non-free and most unlikely to work on local URLs. I thought damn, what I need is a very simple thing! I could code this in 5 minutes! So I did and <a href="http://duoload.verou.me/">here it is</a>, in case someone else finds it useful! The (minuscule amount of) code is of course on <a href="https://github.com/LeaVerou/duoload">Github</a>.</p>
<p><a href="http://duoload.verou.me/" class="call-to-action">Duoload</a></p>
<p>Of course it goes without saying that this is probably a bit inaccurate. Do not use it for mission-critical performance comparisons.</p>
<p>Credits for the name <a href="http://duoload.verou.me/">Duoload</a> to <a href="http://svgees.us/">Chris Lilley</a> who came up with it in the 1 minute deadline I gave him :P</p>
HTML APIs: What they are and how to design a good one2017-02-16T00:00:00Zhttps://lea.verou.me/?p=2702<p>I’m a strong believer in lowering the barrier of what it takes to create rich, interactive experiences and improving the user experience of programming. I wrote <a href="https://www.smashingmagazine.com/2017/02/designing-html-apis/">an article over at Smashing Magazine</a> aimed at JavaScript library developers that want their libraries to be usable via HTML (i.e. without writing any JavaScript). Sounds interesting? <a href="https://www.smashingmagazine.com/2017/02/designing-html-apis/">Read it here</a>.</p>
Introducing Mavo: Create web apps entirely by writing HTML!2017-05-16T00:00:00Zhttps://lea.verou.me/?p=2705<p><img src="http://mavo.io/logo.svg" alt="" />Today <a href="https://www.smashingmagazine.com/2017/05/introducing-mavo/">I finally released</a> the project I’ve been working on for the last two years at <a href="http://csail.mit.edu/">MIT CSAIL</a>: An HTML-based language for creating (many kinds of) web applications without programming or a server backend. It’s named Mavo after my late mother (<strong>Ma</strong>ria <strong>V</strong>er<strong>o</strong>u), and is <a href="https://github.com/mavoweb/mavo">Open Source</a> of course (yes, getting paid to work on open source is exactly as fun as it sounds).</p>
<p>It was the scariest release of my life, and have been postponing it for months. I kept feeling Mavo was not quite there yet, maybe I should add this one feature first, oh and this other one, oh and we can’t release without this one, surely! Eventually I realized that what I was doing had more to do with postponing the anxiety and less to do with Mavo reaching a stage where it can be released. After all, “if you’re not at least a bit embarrassed by what you release, you waited too long”, right?</p>
<p><a href="https://shipitsquirrel.github.io/"><img src="https://shipitsquirrel.github.io/images/ship%20it%20squirrel.png" alt="The infamous Ship It Squrrel" /></a></p>
<p>So, there it is, I hope you find it useful. Read the <a href="https://www.smashingmagazine.com/2017/05/introducing-mavo/">post on Smashing Magazine</a> or just head straight to <a href="http://mavo.io/">mavo.io</a>, read the <a href="http://mavo.io/docs/primer">docs</a>, and play with the <a href="http://mavo.io/demos">demos</a>!</p>
<p>And do let me know what you make with it, no matter how small and trivial you may think it is, I would love to see it!</p>
Different remote and local resource URLs, with Service Workers!2017-10-30T00:00:00Zhttps://lea.verou.me/?p=2735<p>I often run into this issue where I want a different URL remotely and a different one locally so I can test my local changes to a library. Sure, relative URLs work a lot of the time, but are often not an option. Developing <a href="http://lea.verou.me/2017/05/introducing-mavo-create-web-apps-entirely-by-writing-html/">Mavo</a> is yet another example of this: since Mavo is in a separate repo from <a href="https://mavo.io/">mavo.io</a> (its website) as well as <a href="https://test.mavo.io/">test.mavo.io</a> (the testsuite), I can’t just have relative URLs to it that also work remotely. I’ve been encountering this problem way too frequently pretty much since I started in web development. In this post, will describe all solutions and workarounds I’ve used over time for this, including the one I’m currently using for Mavo: Service Workers!</p>
<h3 id="the-manual-one" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2017/10/different-remote-and-local-resource-urls-with-service-workers/#the-manual-one">The manual one</a></h3>
<p>Probably the first solution everyone tries is doing it manually: every time you need to test, you just change the URL to a relative, local one and try to remember to change it back before committing. I still use this in some cases, since us developers are a lazy bunch. Usually I have both and use my editor’s (un)commenting shortcut for enabling one or the other:</p>
<pre><code class="language-markup"><script src="https://get.mavo.io/mavo.js"></script>
<!--<script src="../mavo/dist/mavo.js"></script>-->
</code></pre>
<p>However, as you might imagine, this approach has several problems, the worst of which is that more than once I forgot and committed with the active script being the local one, which resulted in the remote website totally breaking. Also, it’s clunky, especially when it’s two resources whose URLs you need to change.</p>
<h3 id="the-js-one" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2017/10/different-remote-and-local-resource-urls-with-service-workers/#the-js-one">The JS one</a></h3>
<p>This idea uses a bit of JS to load the remote URL when the local one fails to load.</p>
<pre><code class="language-markup"><script src="http://localhost:8000/mavo/dist/mavo.js" onerror="this.src='https://get.mavo.io/mavo.js'"></script>
</code></pre>
<p>This works, and doesn’t introduce any cognitive overhead for the developer, but the obvious drawback is that it slows things down for the server since a request needs to be sent and fail before the real resource can be loaded. Slowing things down for the local case might be acceptable, even though undesirable, but slowing things down on the remote website for the sake of debugging is completely unacceptable. Furthermore, this exposes the debugging URLs in the HTML source, which gives me a bit of a knee jerk reaction.</p>
<p>A variation of this approach that doesn’t have the performance problem is:</p>
<pre><code class="language-markup"><script>
{
let host = location.hostname == "localhost"? 'http://localhost:8000/dist' : 'https://get.mavo.io';
document.write(`<script src="${host}/mavo.js"></scr` + `ipt>`);
}
</script>
</code></pre>
<p>This works fine, but it’s very clunky, especially if you have to do this multiple times (e.g. on multiple testing files or demos).</p>
<h3 id="the-build-tools-one" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2017/10/different-remote-and-local-resource-urls-with-service-workers/#the-build-tools-one">The build tools one</a></h3>
<p>The solution I was following up to a few months ago was to use gulp to copy over the files needed, and then link to my local copies via a relative URL. I would also have a gulp.watch() that monitors changes to the original files and copies them over again:</p>
<pre><code class="language-javascript">gulp.task("copy", function() {
gulp.src(["../mavo/dist/**/*"])
.pipe(gulp.dest("mavo"));
});
gulp.task("watch", function() {
gulp.watch(["../mavo/dist/*"], ["copy"]);
});
</code></pre>
<p>This worked but I had to remember to run <code>gulp watch</code> every time I started working on each project. Often I forgot, which was a huge source of confusion as to why my changes had no effect. Also, it meant I had copies of Mavo lying around on every repo that uses it and had to manually update them by running <code>gulp</code>, which was suboptimal.</p>
<h3 id="the-service-worker-one" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2017/10/different-remote-and-local-resource-urls-with-service-workers/#the-service-worker-one">The Service Worker one</a></h3>
<p>In April, after being fed up with having to deal with this problem for over a decade, I posted <a href="https://twitter.com/LeaVerou/status/857030863292436480">a tweet</a>:</p>
<p><a href="https://twitter.com/LeaVerou/status/857030863292436480?ref_src=twsrc%5Etfw">https://twitter.com/LeaVerou/status/857030863292436480?ref_src=twsrc^tfw</a></p>
<p><a href="https://twitter.com/MylesBorins">@MylesBorins</a> replied (though his tweet seems to have disappeared) and suggested that perhaps Service Workers could help. In case you’ve been hiding under a rock for the past couple of years, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> are a new(ish) API that allows you to intercept requests from your website to the network and do whatever you want with them. They are mostly promoted for creating good offline experiences, though they can do a lot more.</p>
<p>I was looking for an excuse to dabble in Service Workers for a while, and this was a great one. Furthermore, browser support doesn’t really matter in this case because the Service Worker is only used locally.</p>
<p>The code I ended up with looks like this in a small script called <code>sitewide.js</code>, which, as you may imagine, is used sitewide:</p>
<pre><code class="language-javascript">(function() {
if (location.hostname !== "localhost") {
return;
}
if (!self.document) {
// We're in a service worker! Oh man, we’re living in the future! ??
self.addEventListener("fetch", function(evt) {
var url = evt.request.url;
if (url.indexOf("get.mavo.io/mavo.") > -1 || url.indexOf("dev.mavo.io/dist/mavo.") > -1) {
var newURL = url.replace(/.+?(get|dev)\.mavo\.io\/(dist\/)?/, "http://localhost:8000/dist/") + "?" + Date.now();
var response = fetch(new Request(newURL), evt.request)
.then(r => r.status < 400? r : Promise.reject())
// if that fails, return original request
.catch(err => fetch(evt.request));
evt.respondWith(response);
}
});
return;
}
if ("serviceWorker" in navigator) {
// Register this script as a service worker
addEventListener("load", function() {
navigator.serviceWorker.register("sitewide.js");
});
}
})();
</code></pre>
<p>So far, this has worked more nicely than any of the aforementioned solutions and allows me to just use the normal remote URLs in my HTML. However, it’s not without its own caveats:</p>
<ul>
<li>Service Workers are only activated on a cached pageload, so the first one uses the remote URL. This is almost never a problem locally anyway though, so I’m not concerned about it much.</li>
<li>The same origin restriction that service workers have is fairly annoying. So, I have to copy the service worker script on every repo I want to use this on, I cannot just link to it.</li>
<li>It needs to be explained to new contributors since most aren’t familiar with Service Workers and how they work at all.</li>
</ul>
<p>Currently the URLs for both local and remote are baked into the code, but it’s easy to imagine a mini-library that takes care of it as long as you include the local URL as a parameter (e.g. <code>https://get.mavo.io/mavo.js?local=http://localhost:8000/dist/mavo.js</code>).</p>
<h3 id="other-solutions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2017/10/different-remote-and-local-resource-urls-with-service-workers/#other-solutions">Other solutions</a></h3>
<p>Solutions I didn’t test (but you may want to) include:</p>
<ul>
<li><code>.htaccess</code> redirect based on domain, suggested by <a href="https://twitter.com/codepo8">@codepo8</a>. I don’t use Apache locally, so that’s of no use to me.</li>
<li>Symbolic links, suggested by <a href="https://twitter.com/aleschmidx">@aleschmidx</a></li>
<li>User scripts (e.g. Greasemonkey), suggested by <a href="https://twitter.com/WebManWlkg">@WebManWlkg</a></li>
<li>Modifying the hosts file, suggested by <a href="https://twitter.com/LukeBrowell">@LukeBrowell</a> (that works if you don’t need access to the remote URL at all)</li>
</ul>
<p>Is there any other solution? What do you do?</p>
Free Intro to Web Development slides (with demos)2018-02-20T00:00:00Zhttps://lea.verou.me/?p=2750<p><a href="https://projects.verou.me/talks/intro/"><img src="https://lea.verou.me/2018/02/free-intro-to-web-development-slides-with-demos/images/Screen-Shot-2018-02-19-at-22.57.12-300x211.png" alt="" /></a>This semester I’m teaching <a href="http://web.mit.edu/6.813/www/sp18/">6.813 User Interface Design and Implementation at MIT</a>, as an instructor.</p>
<p>Many of the assignments of this course include Web development and the course included two 2-hour labs to introduce students to these technologies. Since I’m involved this year, I decided to make new labs from scratch and increase the number of labs from 2 to 3. Even so, trying to decide what to include and what not to from the entirety of web development in only 6 hours was really hard, and I still feel I failed to include important bits.</p>
<p>Since many people asked me for the slides on Twitter, I decided to share them. <a href="https://projects.verou.me/talks/intro/">You will find my slides here</a> and <a href="http://web.mit.edu/6.813/www/sp18/labs/">an outline of what is covered is here</a>. These slides were also the supporting material the students had on their own laptops and often they had to do exercises in them.</p>
<p>The audience for these slides is <strong>beginners in Web development but technical otherwise</strong> — people who understand OOP, trees, data structures and have experience in at least one C-like programming language.</p>
<p><strong>Some demos will not make sense as they were live coded</strong>, but I included notes (top right or bottom left corner) about what was explained in each part.</p>
<p><strong>Use the arrow keys to navigate.</strong> It is also quite big, so do not open this on a phone or on a data plan.</p>
<p>If the “Open in new Tab” button opens a tab which then closes immediately, <strong>disable Adblock</strong>.</p>
<p>From some quick testing, they seem to work in Firefox and Safari, but in class we were using an updated version of <strong>Chrome</strong> (since we were talking about developer tools, we needed to all have the same UI), so that’s the browser I’d recommend since they were tested much more there.</p>
<p>I’m sharing them as-is in case someone else finds them useful. <strong>Please do not bug me if they don’t work in your setup</strong>, or if you do not find them useful or whatever**.** If they don’t tickle your fancy, move on. I cannot provide any support or fixes. If you want to help fix the issue, you can <a href="https://github.com/leaverou/talks">submit a pull request</a>, but be warned: most of the code was written under extreme time pressure (I had to produce this 6 times as fast as I usually need to make talks), so is not my finest moment.</p>
<p>If you want to use them to teach other people that’s fine as long as it’s a <strong>non-profit</strong> event.</p>
<p>[gallery columns=“2” size=“medium” ids=“2756,2757,2755,2754”]</p>
Quicker Storify export2018-04-27T00:00:00Zhttps://lea.verou.me/?p=2770<p>If you’ve used <a href="http://storify.com/">Storify</a>, you probably know by now it’s closing down soon. They have an <a href="https://storify.com/faq-eol">FAQ</a> up to help people with the transition which explains that to export your content you need to…</p>
<blockquote>
<ol>
<li>Log in to Storify at <a href="https://www.storify.com/">www.storify.com.</a></li>
<li>Mouse over the story that contains content you would like to export and select “View.”</li>
<li>Click on the ellipses icon and select “Export.”</li>
<li>Choose your preferred format for download.</li>
<li>To save your content and linked assets in HTML, select - File > Save as > Web Page, Complete. To export your content to PDF, select Export to HTML > File > Print > Save as PDF.</li>
<li>Repeat the process for each story whose content you would like to preserve.</li>
</ol>
</blockquote>
<p>So I started doing that. I wasn’t sure if JSON or HTML would be more useful to me, so I was exporting both. It was painful. Each export required 3 page loads, and they were slow. After 5 stories, I started wondering if there’s a quicker way. I’m a programmer after all, my job is to automate things. However, I also didn’t want to spend too long on that, since I only had 40 stories, so the effort should definitely not be longer than it would have taken to manually export the remaining 35 stories.</p>
<p>I noticed that the HTML and JSON URLs for each story could actually be recreated by using the slug of the Story URL:</p>
<p><a href="https://storify.com/LeaVerou/**css-variables-var-subtitle-cssconf-asia**.html">https://storify.com/LeaVerou/**css-variables-var-subtitle-cssconf-asia**.html</a>
<a href="https://api.storify.com/v1/stories/LeaVerou/**css-variables-var-subtitle-cssconf-asia">https://api.storify.com/v1/stories/LeaVerou/**css-variables-var-subtitle-cssconf-asia</a>**</p>
<p>The bold part is the only thing that changes. I tried that with a different slug and it worked just fine. Bingo! So I could write a quick console script to get all these URLs and open them in separate tabs and then all I have to do is go through each tab and hit Cmd + S to save. It’s not perfect, but it took minutes to write and saved A LOT of time.</p>
<p>Following is the script I wrote. Go to your profile page, click “Show more” and scroll until all your stories are visible, then paste it into the console. You will probably need to do it twice: once to disable popup blocking because the browser rightfully freaks out when you try to open this many tabs from script, and once to actually open all of them.</p>
<pre><code class="language-javascript">var slugs = [... new Set($$(".story-tile").map(e => e.dataset.path))]
slugs.forEach(s => { open(`https://api.storify.com/v1/stories/${s}`); open(`https://storify.com/${s}.html`) })
</code></pre>
<p>This gets a list of all <strong>unique</strong> (hence the <code>[...new Set(array)]</code>) slugs and opens both the JSON and HTML export URLs in new tabs. Then you can go through each tab and save.</p>
<p>You will notice that the browser becomes REALLY SLOW when you open this many tabs (in my case 41 stories × 2 tabs each = 82 tabs!) so you may want to do it in steps, by using array.slice(). Also, if you don’t want to save the HTML version, the whole process becomes much faster, the HTML pages took AGES to load and kept freezing the browser.</p>
<p>Hope this helps!</p>
<p>PS: If you’re content with your data being held hostage by a different company, you could also use <a href="http://www.wakelet.com/storify">this tool by Wakelet</a>. I’ve done that too, but I also wanted to own my data as well.</p>
Responsive tables, revisited2018-05-14T00:00:00Zhttps://lea.verou.me/?p=2794<p><a href="https://lea.verou.me/2018/05/responsive-tables-revisited/images/Screen-Shot-2018-05-14-at-17.54.45-copy.png"><img src="https://lea.verou.me/2018/05/responsive-tables-revisited/images/Screen-Shot-2018-05-14-at-17.54.45-copy-1024x595.png" alt="Screenshot showing a table with 3 rows turning into 3 sets of key-value pairs" /></a></p>
<p><a href="https://css-tricks.com/responsive-data-tables/">Many people have explored responsive tables</a>. The usual idea is turning the table into key-value pairs so that cells become rows and there are only 2 columns total, which fit in any screen. However, this means table headers need to now be repeated for every row. The current ways to do that are:</p>
<ul>
<li>Duplicating content in CSS or via a data-* attribute, using generated content to insert it before every row.</li>
<li>Using a definition list which naturally has duplicated <code><dt></code>s, displaying it as a table in larger screens.</li>
</ul>
<p>A few techniques that go in an entirely different direction are:</p>
<ul>
<li>Hiding non-essential columns in smaller screens</li>
<li>Showing a thumbnail of the table instead, and display the full table on click</li>
<li>Displaying a graph in smaller screens (e.g. a pie chart)</li>
</ul>
<p>I think the key-value display is probably best because it works for any kind of table, and provides the same information. So I wondered, is there any way to create it without duplicating content either in the markup or in the CSS? After a bit of thinking, I came up with two ways, each with their own pros and cons.</p>
<p>Both techniques are very similar: They set table elements to display: block; so that they behave like normal elements and duplicate the <code><thead></code> contents in two different ways:</p>
<ol>
<li>Using text-shadow and creating one shadow for each row</li>
<li>Using the element() function to duplicate the entire thead, styles and all.</li>
</ol>
<p>Each method has its own pros and cons, but the following pros and cons apply to both:</p>
<ul>
<li><strong>Pros:</strong> Works with normal table markup</li>
<li><strong>Cons:</strong>
<ul>
<li>All but the first set of headers are unselectable (since neither shadows nor element()-generated images are real text). However, keep in mind that the techniques based on generated content also have this problem — and for all rows. Also, that the markup screen readers see is the same as a normal table. However, it’s still a pretty serious flaw and makes this a hack. I’m looking forward to seeing more viable solutions.</li>
<li>Only works if none of the table cells wrap, since it depends on table cells being aligned with their headers.</li>
</ul>
</li>
</ul>
<h3 id="using-text-shadow-to-copy-text-to-other-rows" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2018/05/responsive-tables-revisited/#using-text-shadow-to-copy-text-to-other-rows">Using text-shadow to copy text to other rows</a></h3>
<ul>
<li><strong>Additional Pros:</strong> Works in every browser</li>
<li><strong>Additional Cons:</strong> Max Number of rows needs to be hardcoded in the CSS, since each row needs another text shadow on <code><thead></code>. However, you can specify more shadows than needed, since overflow: hidden on the table prevents extra ones from showing up. Also, number of columns needs to be specified in the CSS (the <code>--cols</code> variable).</li>
</ul>
<p><a href="https://dabblet.com/gist/969a4aa9d53bf6893d72acd422b2e9a8">Demo</a></p>
<iframe src="https://dabblet.com/gist/969a4aa9d53bf6893d72acd422b2e9a8" width="100%" height="500px"></iframe>
<h3 id="using-element()-to-copy-the-entire-%3Cthead%3E-to-other-rows" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2018/05/responsive-tables-revisited/#using-element()-to-copy-the-entire-%3Cthead%3E-to-other-rows">Using element() to copy the entire <code><thead></code> to other rows</a></h3>
<ul>
<li><strong>Additional Cons:</strong> <code>element()</code> is currently only supported in Firefox :(</li>
</ul>
<p><a href="https://dabblet.com/gist/9bce82d186095d4d9b19a469085e9cb3">Demo</a></p>
<iframe src="https://dabblet.com/gist/9bce82d186095d4d9b19a469085e9cb3" width="100%" height="500px"></iframe>
Never forget type="button" on generated buttons!2018-05-19T00:00:00Zhttps://lea.verou.me/?p=2801<p><img src="https://i.imgflip.com/2am9er.jpg" alt="" /> I just dealt with one of the weirdest bugs and thought you may find it amusing too.</p>
<p>In one of my slides for my upcoming talk “Even More CSS Secrets”, I had a <a href="https://mavo.io/">Mavo</a> app on a <code><form></code>, and the app included a collection to quickly create a UI to manage pairs of values for something I wanted to calculate in one of my live demos. A Mavo collection is a repeatable HTML element with affordances to add items, delete items, move items etc. Many of these affordances are implemented via <code><button></code> elements generated by Mavo.</p>
<p>Normally, hitting Enter inside a text field within a collection adds a new item, as one would expect. However, I noticed that when I hit Enter inside any item, not only no item was added, but an item was being <strong>deleted</strong>, with the usual “Item deleted [Undo]” UI and everything!</p>
<p>At first I thought it was a bug with the part of Mavo code that adds items on Enter and deletes empty items on backspace, so I commented that out. Nope, still happening. I was already very puzzled, since I couldn’t remember any other part of the codebase that deletes items in response to keyboard events.</p>
<p>So, I added breakpoints on the <code>delete(item)</code> method of <code>Mavo.Collection</code> to inspect the call stack and see how execution got there. Turned out, it got there via a normal …<code>click</code> event on the actual delete button! What fresh hell was this? I never clicked any delete button!</p>
<p>And then it dawned on me: <strong><a href="https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element"><code><button></code> elements with no <code>type</code> attribute set are submit buttons by default</a>!</strong> Quote from spec: <em>The missing value default and invalid value default are the Submit Button state.</em>. This makes no difference in most cases, <strong>UNLESS</strong> you’re inside a form. The delete button of the first item had been turned into the de facto default submit button just because it was the first button in that form and it had no type!</p>
<p>I also remembered that <strong>regardless of how you submit a form (e.g. by hitting Enter on a single-line text field) it also fires a click event on the default submit button</strong>, because people often listen to that instead of the form’s submit event. Ironically, I was cancelling the form’s submit event in my code, but it still generated that fake click event, making it even harder to track down as no form submission was actually happening.</p>
<p>The solution was of course to <a href="https://github.com/mavoweb/mavo/commit/9c1e6fcd31a52bc10f1f08f4d1ebf47eac72ac3b">go through every part of the Mavo code that generates buttons and add type=“button” to them</a>. I would recommend this to everyone who is writing libraries that will operate in unfamiliar HTML code. Most of the time a type-less <code><button></code> will work just fine, but when it doesn’t, things get <em>really weird</em>.</p>
Easy Dynamic Regular Expressions with Tagged Template Literals and Proxies2018-06-04T00:00:00Zhttps://lea.verou.me/?p=2817<p>If you use regular expressions a lot, you probably also create them from existing strings that you first need to escape in case they contain special characters that need to be matched literally, like <code>$</code> or <code>+</code>. Usually, a helper function is defined (hopefully this will soon change as <a href="https://github.com/benjamingr/RegExp.escape/">RegExp.escape()</a> is coming!) that basically looks like this:</p>
<pre><code class="language-js">var escapeRegExp = s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
</code></pre>
<p>and then regexps are created by escaping the static strings and concatenating them with the rest of the regex like this:</p>
<pre><code class="language-js">var regex = RegExp(escapeRegExp(start) + '([\\S\\s]+?)' + escapeRegExp(end), "gi")
</code></pre>
<p>or, with ES6 template literals, like this:</p>
<pre><code class="language-js">var regex = RegExp(`${escapeRegExp(start)}([\\S\\s]+?)${escapeRegExp(end)}`, "gi")
</code></pre>
<p>(In case you were wondering, this regex is taken directly from the <a href="https://github.com/mavoweb/mavo/blob/master/src/expression.js#L48">Mavo source code</a>)</p>
<p>Isn’t this horribly verbose? What if we could define a regex with just a template literal (<code>`${start}([\\S\\s]+?)${end}`</code> for the regex above) and it just worked? Well, it turns out we can! If you haven’t seen <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates">tagged template literals</a> before, I suggest you click that MDN link and read up. Basically, you can prepend an ES6 template literal with a reference to a function and the function accepts the static parts of the string and the dynamic parts separately, allowing you to operate on them!</p>
<p>So, what if we defined such a function that returns a RegExp object and escapes the dynamic parts? Let’s try to do that:</p>
<pre><code class="language-js">var regexp = (strings, ...values) => {
return RegExp(strings[0] + values.map((v, i) => escapeRegExp(v) + strings[i+1]).join(""))
};
</code></pre>
<p>And now we can try it in the console:</p>
<pre><code class="language-js">> regexp`^${'/*'}([\\S\\s]+?)${'*/'}`;
< /^\/\*([\S\s]+?)\*\//
</code></pre>
<h3 id="won%E2%80%99t-somebody%2C-please%2C-think-of-the-flags%3F!" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2018/06/easy-dynamic-regular-expressions-with-tagged-template-literals-and-proxies/#won%E2%80%99t-somebody%2C-please%2C-think-of-the-flags%3F!">Won’t somebody, please, think of the flags?!</a></h3>
<p>This is all fine and dandy, but how do we specify flags? Note that the original regexp had flags (“gi”). The tagged template syntax doesn’t really allow us to pass in any additional parameters. However, thanks to functions being first-class objects in JS, we can have a function that takes the flags in as parameters and returns a function that generates regexps with the right flags:</p>
<pre><code class="language-js">var regexp = flags => {
return (strings, ...values) => {
var pattern = strings[0] + values.map((v, i) => escapeRegExp(v) + strings[i+1]).join("")
return RegExp(pattern, flags);
}
};
</code></pre>
<p>And now we can try it in the console:</p>
<pre><code class="language-js">> regexp("gi")`^${'/*'}([\\S\\s]+?)${'*/'}`;
< /^\/\*([\S\s]+?)\*\//gi
</code></pre>
<p>This works nice, but now even if we don’t want any flags, we can’t use the nice simple syntax we had earlier, we need to include a pair of empty parens:</p>
<pre><code class="language-js">> regexp()`^${'/*'}([\\S\\s]+?)${'*/'}`;
< /^\/\*([\S\s]+?)\*\//
</code></pre>
<p>Can we have our cake and eat it too? Can we have the short parenthesis-less syntax when we have no flags, and still be able to specify flags? Of course! We can check the arguments we have and either return a function, or call the function. If our function is used as a tag, the first argument will be an array (<a href="http://lea.verou.me/2018/06/easy-dynamic-regular-expressions-with-tagged-template-literals-and-proxies/#comment-3930513790">thanks Roman!</a>). If we’re expecting it to return a function, the first argument would be a string: the flags. So, let’s try this approach!</p>
<pre><code class="language-js">var regexp = (...args) => {
var ret = (flags, strings, ...values) => {
var pattern = strings[0] + values.map((v, i) => escapeRegExp(v) + strings[i+1]).join("");
return RegExp(pattern, flags);
};
if (Array.isArray(args[0])) {
// Used as a template tag
return ret("", ...args);
}
return ret.bind(undefined, args[0]);
};
</code></pre>
<p>And now we can try it in the console and verify that both syntaxes work:</p>
<pre><code class="language-js">> regexp("gi")`^${'/*'}([\\S\\s]+?)${'*/'}`;
< /^\/\*([\S\s]+?)\*\//gi
regexp`^${'/*'}([\\S\\s]+?)${'*/'}`;
< /^\/\*([\S\s]+?)\*\//
</code></pre>
<h3 id="even-nicer-syntax%2C-with-proxies!" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2018/06/easy-dynamic-regular-expressions-with-tagged-template-literals-and-proxies/#even-nicer-syntax%2C-with-proxies!">Even nicer syntax, with proxies!</a></h3>
<p>Is there a better way? If this is not super critical for performance, we could use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">proxies</a> to return the right function with a template tag like <code>regexp.gi</code>, no parentheses or quotes needed and the code is actually shorter too:</p>
<pre><code class="language-js">var _regexp = (flags, strings, ...values) => {
var pattern = strings[0] + values.map((v, i) => escapeRegExp(v) + strings[i+1]).join("");
return RegExp(pattern, flags);
};
var regexp = new Proxy(_regexp.bind(undefined, ""), {
get: (t, property) => _regexp.bind(undefined, property)
});
</code></pre>
<p>And now we can try it in the console, both with and without flags!</p>
<pre><code class="language-js">> regexp.gi`^${'/*'}([\\S\\s]+?)${'*/'}`;
< /^\/\*([\S\s]+?)\*\//gi
> regexp`^${'/*'}([\\S\\s]+?)${'*/'}`;
< /^\/\*([\S\s]+?)\*\//
</code></pre>
<p>That’s some beauty right there! ?</p>
<p><em>PS: If you liked this, take a look at <a href="http://2ality.com/2017/07/re-template-tag.html">this mini-library</a> by Dr. Axel Rauschmayer that uses a similar idea and turns it into a library that does more than just escaping strings (different syntax for flags though, they become part of the template string, like in PHP)</em></p>
Refresh CSS Bookmarklet v22018-09-18T00:00:00Zhttps://lea.verou.me/?p=2838<p>Almost 11 years ago, <a href="https://www.paulirish.com/2008/how-to-iterate-quickly-when-debugging-css/">Paul Irish posted this brilliant bookmarklet</a> to refresh all stylesheets on the current page. Despite the amount of tools, plugins, servers to live reload that have been released over the years, I’ve always kept coming back to it. It’s incredibly elegant in its simplicity. It works everywhere: locally or remotely, on any domain and protocol. No need to set up anything, no need to alter my process in any way, no need to use a specific local server or tool. It quietly just accepts your preferences and workflow instead of trying to change them. Sure, it doesn’t automatically detect changes and reload, but in most cases, I don’t want it to.</p>
<p>I’ve been using this almost daily for a decade and there’s always been one thing that bothered me: It doesn’t work with iframes. If the stylesheet you’re editing is inside an iframe, tough luck. If you can open the frame in a new tab, that works, but often that’s nontrivial (e.g. the frame is dynamically generated). After dealing with this issue today once more, I thought “this is just a few lines of JS, why not fix it?”.</p>
<p>The first step was to get Paul’s code in a readable format, since the bookmarklet is heavily minified:</p>
<pre><code>(function() {
var links = document.getElementsByTagName('link');
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel.toLowerCase().match(/stylesheet/) && link.href) {
var href = link.href.replace(/(&|%5C?)forceReload=\d+/, '');
link.href = href + (href.match(/\?/) ? '&' : '?') + 'forceReload=' + (new Date().valueOf())
}
}
})()
</code></pre>
<p>Once I did that, it became obvious to me that this could be shortened a lot; the last 10 years have been wonderful for JS evolution!</p>
<pre><code>(()=>{
for (let link of Array.from(document.querySelectorAll("link[rel=stylesheet][href]"))) {
var href = new URL(link.href, location);
href.searchParams.set("forceReload", Date.now());
link.href = href;
}
})()
</code></pre>
<p>Sure, this reduces browser support a bit (most notably it excludes IE11), but since this is a local development tool, that’s not such a big problem.</p>
<p>Now, let’s extend this to support iframes as well:</p>
<pre><code>{
let $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector));
let refresh = (document) => {
for (let link of $$("link[rel=stylesheet][href]", document)) {
let href = new URL(link.href);
href.searchParams.set("forceReload", Date.now());
link.href = href;
}
for (let iframe of $$("iframe", document)) {
iframe.contentDocument && refresh(iframe.contentDocument);
}
}
refresh();
}
</code></pre>
<p>That’s it! Do keep in mind that this will not work with cross-origin iframes, but then again, you probably don’t expect it to in that case.</p>
<p>Now all we need to do to turn it into a bookmarklet is to prepend it with <code>javascript:</code> and minify the code. Here you go:</p>
<p>[Refresh CSS](javascript:{let e=(e,t=document)=>Array.from(t.querySelectorAll(e)),t=r=>{for(let t of e(‘link[rel=stylesheet][href]’,r)){let e=new URL(t.href);e.searchParams.set(‘forceReload’,Date.now()),t.href=e}for(let o of e(‘iframe’,r))o.contentDocument&&t(o.contentDocument)};t()})</p>
<p>Hope this is useful to someone else as well :) Any improvements are always welcome!</p>
<h4 id="credits" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2018/09/refresh-css-bookmarklet-v2/#credits">Credits</a></h4>
<ul>
<li>Paul Irish, for the original bookmarklet</li>
<li>Maurício Kishi, for making the iframe traversal recursive (<a href="http://lea.verou.me/2018/09/refresh-css-bookmarklet-v2/#comment-4102700684">comment</a>)</li>
</ul>
ReferenceError: x is not defined?2018-12-14T00:00:00Zhttps://lea.verou.me/?p=2863<p>Today for a bit of code I was writing, I needed to be able to distinguish “x is not defined” <code>ReferenceError</code>s from any other error within a <code>try...catch</code> block and handle them differently.</p>
<p>Now I know what you’re thinking. Trying to figure out exactly what kind of error you have programmatically is a well-known fool’s errand. If you express a desire to engage in such a risky endeavor, any JS veteran in sight will shake their head in remembrance of their early days, but have the wisdom to refrain from trying to convince you otherwise; they know that failing will teach you what it taught them when they were young and foolish enough to attempt such a thing.</p>
<p>Despite writing JS for 13 years, today I was feeling adventurous. “But what if, just this once, I could get it to work? It’s a pretty standard error message! What if I tested in so many browsers that I would be confident I’ve covered all cases?”</p>
<p>I made <a href="https://codepen.io/leaverou/pen/aPdGeN?editors=0110#0">a simple page on my server that just prints out the error message</a> written in a way that would maximize older browser coverage. Armed with that, I started visiting every browser in my <a href="https://browserstack.com/">BrowserStack</a> account. Here are my findings for anyone interested:</p>
<ul>
<li>Chrome (all versions, including mobile): <code>x is not defined</code></li>
<li>Firefox (all versions, including mobile): <code>x is not defined</code></li>
<li>Safari 4-12 : <code>Can't find variable: x</code></li>
<li>Edge (16 - 18): <code>'x' is not defined</code></li>
<li>Edge 15: <code>'x' is undefined</code></li>
<li>IE6-11 and Windows Phone IE: <code>'x' is undefined</code></li>
<li>UC Browser (all versions): <code>x is not defined</code></li>
<li>Samsung browser (all versions): <code>x is not defined</code></li>
<li>Opera Mini and Pre-Chromium Opera: <code>Undefined variable: x</code></li>
</ul>
<p>Even if you, dear reader, are wise enough to never try and detect this error, I thought you may find the variety (or lack thereof) above interesting.</p>
<p>I also did a little bit of testing with a different UI language (I picked Greek), but it didn’t seem to localize the error messages. If you’re using a different UI language, please open the page above and if the message is not in English, let me know!</p>
<p>In the end, I decided to go ahead with it, and time will tell if it was foolish to do so. For anyone wishing to also dabble in such dangerous waters, this was my checking code:</p>
<pre><code class="language-js">if (e instanceof ReferenceError
&& /is (not |un)defined$|^(Can't find|Undefined) variable/.test(e.message)) {
// do stuff
}
</code></pre>
<p>Found any cases I missed? Or perhaps you found a different <code>ReferenceError</code> that would erroneously match the regex above? Let me know in the comments!</p>
<p>One thing that’s important to note is that even if the code above is bulletproof for today’s browser landscape, <strong>the more developers that do things like this, the harder it is for browser makers to improve these error messages</strong>. However, until there’s a better way to do this, pointing fingers at developers for wanting to do perfectly reasonable things, is not the solution. <strong>This is why HTTP has status codes</strong>, so we don’t have to string match on the text. Imagine having to string match “Not Found” to figure out if a request was found or not! Similarly, many other technologies have error codes, so that different types of errors can be distinguished without resulting to flimsy string matching. I’m hoping that one day JS will also have a better way to distinguish errors more precisely than the general error categories of today, and we’ll look back to posts like this with a nostalgic smile, being so glad we don’t have to do crap like this ever again.</p>
Utility: Convert SVG path to all-relative or all-absolute commands2019-05-06T00:00:00Zhttps://lea.verou.me/?p=2895<p>I like hand-editing my SVGs. Often I will create an initial version in Illustrator, and then export and continue with hand editing. Not only is it a bit of a meditative experience and it satisfies my obsessive-compulsive tendencies to clean up the code, it has actual practical benefits when you need to make certain changes or introduce animation. Some things are easier to do in a GUI, and others are easier to do in code, and I like having the flexibility to pick which one fits my use case best.</p>
<p>However, there was always a thing that was a PITA: modifying paths. Usually if I need anything more complicated than just moving them, I’d do it in Illustrator, but even moving them can be painful if they are not all relative (and no, I don’t like introducing pointless transforms for things that should really be in the <code>d</code> attribute).</p>
<p>For example, this was today’s result of trying to move an exported “a” glyph from <a href="https://fonts.google.com/specimen/Raleway">Raleway Bold</a> by modifying its first <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#MoveTo_path_commands">M</a> command:</p>
<p><img src="https://lea.verou.me/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/images/image.png" alt="" /></p>
<p>Trying to move a path by changing its first M command when not all of its commands are relative.</p>
<p>This happened because even though <em>most</em> commands were exported as relative, several were not and I had not noticed. I have no idea why some commands were exported as absolute, it seems kind of random.</p>
<p>When all commands are relative, moving a path is as simple as manipulating its initial <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#MoveTo_path_commands">M command</a> and the rest just adapts, because <strong>that’s the whole point of relative commands</strong>. Same with manipulating every other part of the path, the rest of it just adapts. It’s beautiful. I honestly have no idea why anybody would favor absolute commands. And yet, googling “convert SVG path to relative” yields <a href="https://stackoverflow.com/questions/14179333/convert-svg-path-to-relative-commands">one result</a>, whereas there are plenty of results about converting paths to absolute. No idea why that’s even desirable, ever (?).</p>
<p>I remembered I had come across that result before. Thankfully, there’s also a <a href="http://jsfiddle.net/MC53K/">fiddle</a> to go with it, which I had used in the past to convert my path. I love it, it uses this library called <a href="http://snapsvg.io/">Snap.svg</a> which supports converting paths to relative as a <em>just-add-water</em> <a href="http://snapsvg.io/docs/#Snap.path.toRelative">utility method</a>. However, that fiddle is a quick demo to answer a StackOverflow question, so the UI is not super pleasant to use (there is no UI: you just manipulate the path in the SVG and wait for the fiddle to run). This time around, I needed to convert multiple paths, so I needed a more efficient UI.</p>
<p>So I created <a href="https://codepen.io/leaverou/full/RmwzKv">this demo</a> which is also based on Snap.svg, but has a slightly more efficient UI. You just paste your path in a textarea and it both displays it and instantly converts it to all-relative and all-absolute paths (also using Snap.svg). It also displays both your original path and the two converted ones, so you can make sure they still look the same. It even follows a pending-delete pattern so you can just focus on the output textarea and hit Cmd-C in one fell swoop.</p>
<p><img src="https://lea.verou.me/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/images/image-1-1024x238.png" alt="" /></p>
<p>I wasn’t sure about posting this or just tweeting it (it literally took less than 30 minutes — including this blog post — and I tend to only post small things like that on <a href="https://twitter.com/leaverou">my twitter</a>), but I thought it might be useful to others googling the same thing, so I may as well post it here for posterity. Enjoy!</p>
<iframe src="https://codepen.io/leaverou/full/RmwzKv" width="100%" height="600" style="border: none"></iframe>
Issue closing stats for any repo2019-12-13T00:00:00Zhttps://lea.verou.me/?p=2902<p><strong><em>tl;dr:</em></strong> <em>If you just want to quickly get stats for a repo, you can find the app</em> <a href="https://projects.verou.me/issue-closing/"><em>here</em></a><em>. The rest of this post explains how it’s built with Mavo HTML, CSS, and 0 lines of JS.</em> Or, if you’d prefer, you can just View Source — it’s all there!</p>
<p><img src="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/images/image.png" alt="" /></p>
<p>The finished app we’re going to make, find it at <a href="https://projects.verou.me/issue-closing">https://projects.verou.me/issue-closing</a></p>
<p>One of the cool things about <a href="https://mavo.io/">Mavo</a> is how it enables one to quickly build apps that utilize the Github API. At some point I wanted to compute stats about how quickly (or rather, slowly…) Github issues are closed in the <a href="https://github.com/mavoweb/mavo">Mavo repo</a>. And what better way to build this than a Mavo app? It was fairly easy to build a prototype for that.</p>
<h2 id="displaying-a-list-of-the-last-100-closed-issues-and-the-time-it-took-to-close-them" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#displaying-a-list-of-the-last-100-closed-issues-and-the-time-it-took-to-close-them">Displaying a list of the last 100 closed issues and the time it took to close them</a></h2>
<p>To render the last 100 closed issues in the Mavo app, I first looked up <a href="https://developer.github.com/v3/issues/#list-issues-for-a-repository">the appropriate API call in Github’s API documentation</a>, then used it in the <code>mv-source</code> attribute on the <em>Mavo root</em>, i.e. the element with <code>mv-app</code> that encompasses everything in my app:</p>
<pre><code class="language-html"><div mv-app="issueClosing"
mv-source="https://api.github.com/repos/mavoweb/mavo/issues?state=closed&sort=updated&per_page=100"
mv-mode="read">
<!-- app here -->
</div>
</code></pre>
<p>Then, I displayed a list of these issues with:</p>
<pre><code class="language-html"><div mv-multiple property="issue">
<a class="issue-number" href="https://github.com/mavoweb/mavo/issues/[number]" title="[title]" target="_blank">#[number]</a>
took [closed_at - created_at] ms
</div>
</code></pre>
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="leaverou" data-slug-hash="qBEaoPL" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Step 1 - Issue Closing App Tutorial"><span>See the Pen <a href="https://codepen.io/leaverou/pen/qBEaoPL">Step 1 - Issue Closing App Tutorial</a> by Lea Verou (<a href="https://codepen.io/leaverou">@leaverou</a>) on <a href="https://codepen.io/">CodePen</a>.</span></p>
<script async="" src="https://static.codepen.io/assets/embed/ei.js"></script>
<p>This would work, but the way it displays results is not very user friendly (e.g. <em>“#542 took 149627000 ms”</em>). We need to display the result in a more readable way.</p>
<p>We can use the <a href="https://mavo.io/docs/functions/#duration"><code>duration()</code></a> function to display a readable duration such as “1 day”:</p>
<pre><code class="language-html"><div mv-multiple property="issue">
<a class="issue-number" href="https://github.com/mavoweb/mavo/issues/[number]" title="[title]" target="_blank">#[number]</a>
took [duration(closed_at - created_at)]
</div>
</code></pre>
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="leaverou" data-slug-hash="bGbBQwg" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Step 2 - Issue Closing App Tutorial"><span>See the Pen <a href="https://codepen.io/leaverou/pen/bGbBQwg">Step 2 - Issue Closing App Tutorial</a> by Lea Verou (<a href="https://codepen.io/leaverou">@leaverou</a>) on <a href="https://codepen.io/">CodePen</a>.</span></p>
<script async="" src="https://static.codepen.io/assets/embed/ei.js"></script>
<h2 id="displaying-aggregate-statistics" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#displaying-aggregate-statistics">Displaying aggregate statistics</a></h2>
<p>However, a list of issues is not very easy to process. What’s the overall picture? Does this repo close issues fast or not? Time for some statistics! We want to calculate average, median, minimum and maximum issue closing time. To calculate these statistics, we need to use the times we have displayed in the previous step.</p>
<p>First, we need to give our calculation a name, so we can refer to its value in expressions:</p>
<pre><code class="language-html"><span property="timeToClose">[duration(closed_at - created_at)]</span>
</code></pre>
<p>However, as it currently stands, the value of this property is text (e.g. “1 day”, “2 months” etc). We cannot compute averages and medians on text! We need the property value to be a number. We can hide the actual raw value in an attribute and use the nicely formatted value as the visible content of the element, like so (we use the <code>content</code> attribute here but you can use any, e.g. a <code>data-*</code> attribute would work just as well):</p>
<pre><code class="language-html"><span property="timeToClose" mv-attribute="content" content="[closed_at - created_at]">[duration(timeToClose)]</span>
</code></pre>
<p><em>Note: There is</em> <a href="https://github.com/mavoweb/mavo/issues/444"><em>a data formatting feature in the works</em></a> <em>which would simplify this kind of thing by allowing you to separate the raw value and its presentation without having to use separate attributes for them.</em></p>
<p>We can also add a class to color it red, green, or black depending on whether the time is longer than a month, shorter than a day, or in-between respectively:</p>
<pre><code class="language-html"><span property="timeToClose" mv-attribute="content" content="[closed_at - created_at]" class="[if(timeToClose > month(), 'long', if (timeToClose < day(), 'short'))]">[duration(timeToClose)]</span>
</code></pre>
<p>Now, on to calculate our statistics! We take advantage of the fact that <code>timeToClose</code> outside the <code>issue</code> collection gives us <strong>all</strong> the times, so we can compute aggregates on them. Therefore, the stats we want to calculate are simply <code>average(timeToClose)</code>, <code>median(timeToClose)</code>, <code>min(timeToclose)</code>, and <code>max(timeToClose)</code>. We put all these in a definition list:</p>
<pre><code class="language-html"><dl>
<dt>Median</dt>
<dd>[duration(median(timeToClose))]</dd>
<dt>Average</dt>
<dd>[duration(average(timeToClose))]</dd>
<dt>Slowest</dt>
<dd>[duration(max(timeToClose))]</dd>
<dt>Fastest</dt>
<dd>[duration(min(timeToClose))]</dd>
</dl>
</code></pre>
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="leaverou" data-slug-hash="jONVQrw" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Step 3 - Issue Closing App Tutorial"><span>See the Pen <a href="https://codepen.io/leaverou/pen/jONVQrw/">Step 3 - Issue Closing App Tutorial</a> by Lea Verou (<a href="https://codepen.io/leaverou">@leaverou</a>) on <a href="https://codepen.io/">CodePen</a>.</span></p>
<script async="" src="https://static.codepen.io/assets/embed/ei.js"></script>
<h2 id="making-repo-a-variable" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#making-repo-a-variable">Making repo a variable</a></h2>
<p>Now that all the functionality of my app was in place, I realized this could be useful for more repos as well. Why not make the repo a property that can be changed? So I added an input for specifying the repo: <code><input property="repo" mv-default="mavoweb/mavo"></code> and then replaced <code>mavoweb/mavo</code> with <code>[repo]</code> everywhere else, i.e. <code>mv-source</code> became <code>https://api.github.com/repos/[repo]/issues?state=closed&sort=updated&per_page=100</code>.</p>
<h2 id="avoid-reload-on-every-keystroke" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#avoid-reload-on-every-keystroke">Avoid reload on every keystroke</a></h2>
<p>This worked, but since Mavo properties are reactive, it kept trying to reload data with every single keystroke, which was annoying and wasteful. Therefore, I needed to do a bit more work so that there is a definite <em>action</em> that submits the change. Enter <a href="https://mavo.io/docs/actions">Mavo Actions</a>!</p>
<p>I created two properties: <code>repo</code> for the actual repo and <code>repoInput</code> for the input. <code>repoInput</code> still changes on every keystroke, but it’s <code>repo</code> that is actually being used in the app. I wrapped the input with a <code><form></code> and added an action on the form that does this (<code>mv-action="set(repo, repoInput)"</code>). I also added a submit button. Since Mavo actions on forms are triggered when the form is submitted, it doesn’t matter if I press Enter on the input, or click the Submit button, both work.</p>
<h2 id="setting-the-repo-via-a-url-parameter" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#setting-the-repo-via-a-url-parameter">Setting the repo via a URL parameter</a></h2>
<p>Eventually I also wanted to be able to set the repo from the URL, so I also added a hidden <code>repoDefault</code> property: <code><meta property="repoDefault" content="[url('repo') or 'mavoweb/mavo']"></code>, and then changed the hardcoded <code>mv-default="mavoweb/mavo"</code> to <code>mv-default="[repoDefault]"</code> on both the <code>repo</code> and the <code>repoInput</code> properties. That way one can link to stats for a specific repo, e.g. <a href="https://projects.verou.me/issue-closing/?repo=prismjs/prism">https://projects.verou.me/issue-closing/?repo=prismjs/prism</a></p>
<p>Why a <code>repoDefault</code> property and not just <code>mv-default="[url('repo') or 'mavoweb/mavo']</code>? Just keeping things <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> and avoiding having to repeat the same expression twice.</p>
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="leaverou" data-slug-hash="bGbBGXM" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Step 5 - Issue Closing App Tutorial"><span>See the Pen <a href="https://codepen.io/leaverou/pen/bGbBGXM/">Step 5 - Issue Closing App Tutorial</a> by Lea Verou (<a href="https://codepen.io/leaverou">@leaverou</a>) on <a href="https://codepen.io/">CodePen</a>.</span></p>
<script async="" src="https://static.codepen.io/assets/embed/ei.js"></script>
<h2 id="filtering-by-label" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#filtering-by-label">Filtering by label</a></h2>
<p>At some point I wondered: What would the issue closing times be if we only counted bugs? What if we only counted enhancements? Surely these would be different: When looking at issue closing times for a repo, one primarily cares about how fast bugs are fixed, not how quickly every random feature suggestion is implemented. Wouldn’t it be cool to also have a label filter?</p>
<p>For that, I added a series of radio buttons:</p>
<pre><code class="language-html">Show:
<label><input type="radio" property="labels" name="labels" checked value=""> All</label>
<label><input type="radio" name="labels" value="bug"> Bugs only</label>
<label><input type="radio" name="labels" value="enhancement"> Enhancements only</label>
</code></pre>
<p>Then, I modified <code>mv-source</code> to also use this value in its API call: <code>mv-source="https://api.github.com/repos/[repo]/issues?state=closed&sort=updated&labels=[labels]&per_page=100"</code>.</p>
<p>Note that when turning radio buttons into a Mavo property you only use the <code>property</code> attribute on the first one. This is important because Mavo has special handling when you use the <code>property</code> attribute with the same name multiple times in the same group, which we don’t want here. You can add the <code>property</code> attribute on any of the radio buttons, it doesn’t have to be the first. Just make sure it’s only one of them.</p>
<p>Then I became greedy: Why not also allow filtering by custom labels too? So I added another radio with an input:</p>
<pre><code class="language-html">Show:
<label><input type="radio" property="labels" name="labels" checked value=""> All</label>
<label><input type="radio" name="labels" value="bug"> Bugs only</label>
<label><input type="radio" name="labels" value="enhancement"> Enhancements only</label>
<label><input type="radio" name="labels" value="[customLabel]"> Label <input property="customLabel"></label>
</code></pre>
<p>Note that since this is a text field, when the last value is selected, we’d have the same problem as we did with the repo input: Every keystroke would fire a new request. We can solve this in the same way as we solved it for the <code>repo</code> property, by having an intermediate property and only setting <code>labels</code> when the form is actually submitted:</p>
<pre><code class="language-html">Show:
<label><input type="radio" property="labelFilter" name="labels" checked value=""> All</label>
<label><input type="radio" name="labels" value="bug"> Bugs only</label>
<label><input type="radio" name="labels" value="enhancement"> Enhancements only</label>
<label><input type="radio" name="labels" value="[customLabel]"> Label <input property="customLabel"></label>
<meta property="labels" content="">
</code></pre>
<h2 id="adding-label-autocomplete" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#adding-label-autocomplete">Adding label autocomplete</a></h2>
<p>Since we now allow filtering by a custom label, wouldn’t it be cool to allow autocomplete too? HTML allows us to offer autocomplete in our forms via <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"><code><datalist></code></a> and we can use Mavo to populate the contents!</p>
<p>First, we add a <code><datalist></code> and link it with our custom label input, like so:</p>
<pre><code class="language-html"><label><input type="radio" name="labels" value="[customLabel]"> Label <input property="customLabel" list="label-suggestions"></label>
<datalist id="label-suggestions">
</datalist>
</code></pre>
<p>Currently, our suggestion list is empty. How do we populate it with the labels that have actually been used in this repo? Looking at the <a href="https://developer.github.com/v3/issues/#response-1">API documentation</a>, we see that each returned issue has a <code>labels</code> field with its labels as an object, and each of these objects has a <code>name</code> field with the textual label. This means that if we use <code>issue.labels.name</code> in Mavo outside of the issues collection, we get a list with <strong>all</strong> of these values, which we can then use to populate our <code><datalist></code> by passing it on to <a href="https://mavo.io/docs/expressions/#mv-value"><code>mv-value</code></a> which allows us to create dynamic collections:</p>
<pre><code class="language-html"><label><input type="radio" name="labels" value="[customLabel]"> Label <input property="customLabel" list="label-suggestions"></label>
<datalist id="label-suggestions">
<option mv-multiple mv-value="unique(issue.labels.name)"></option>
</datalist>
</code></pre>
<p>Note that we also used <a href="https://mavo.io/docs/functions/#unique"><code>unique()</code></a> to eliminate duplicates, since otherwise each label would appear as many times as it is used.</p>
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="leaverou" data-slug-hash="QWLGWXV" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Issue Closing App - Tutorial Step 6"><span>See the Pen <a href="https://codepen.io/leaverou/pen/QWLGWXV/">Issue Closing App - Tutorial Step 6</a> by Lea Verou (<a href="https://codepen.io/leaverou">@leaverou</a>) on <a href="https://codepen.io/">CodePen</a>.</span></p>
<script async="" src="https://static.codepen.io/assets/embed/ei.js"></script>
<h2 id="adding-a-visual-summary-graphic" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/#adding-a-visual-summary-graphic">Adding a visual summary graphic</a></h2>
<p>Now that we got the functionality down, we can be a little playful and add some visual flourish. How about a bar chart that summarizes the proportion of long vs short vs normal closing times? We start by setting the CSS variables we are going to need for our graphic, i.e. the number of issues in each category:</p>
<pre><code class="language-html"><summary style="--short: [count(timeToClose < day())]; --long: [count(timeToClose > month())]; --total: [count(issue)];">
Based on [count(issue)] most recently updated issues
</summary>
</code></pre>
<p>Then, we draw our graphic:</p>
<pre><code class="language-css">summary::before {
content: "";
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
height: 5px;
background: linear-gradient(to right, var(--short-color) calc(var(--short, 0) / var(--total) * 100%), hsl(220, 10%, 75%) 0, hsl(220, 10%, 75%) calc(100% - var(--long, 0) / var(--total) * 100%), var(--long-color) 0) bottom / auto 100% no-repeat border-box;
}
</code></pre>
<p>Now, wouldn’t it be cool to also show a small pie chart next to the heading, if conic gradients are supported so we can draw it? The color stops would be the same, so we define a <code>--summary-stops</code> variable on <code>summary</code>, so we can reuse them across both gradients:</p>
<pre><code class="language-css">summary {
--summary-stops: var(--short-color) calc(var(--short, 0) / var(--total) * 100%), hsl(220, 10%, 75%) 0, hsl(220, 10%, 75%) calc(100% - var(--long, 0) / var(--total) * 100%), var(--long-color) 0;
}
summary::before {
content: "";
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
height: 5px;
background: linear-gradient(to right, var(--summary-stops)) bottom / auto 100% no-repeat border-box;
}
@supports (background: conic-gradient(red, red)) {
summary::after {
content: "";
display: inline-block;
vertical-align: middle;
width: 1.2em;
height: 1.2em;
margin-left: .3em;
border-radius: 50%;
background: conic-gradient(var(--summary-stops));
}
}
</code></pre>
<p class="codepen" data-height="471" data-theme-id="light" data-default-tab="html,result" data-user="leaverou" data-slug-hash="QWLGWzx" style="height: 471px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Issue Closing App - Tutorial Step 7"><span>See the Pen <a href="https://codepen.io/leaverou/pen/QWLGWzx/">Issue Closing App - Tutorial Step 7</a> by Lea Verou (<a href="https://codepen.io/leaverou">@leaverou</a>) on <a href="https://codepen.io/">CodePen</a>.</span></p>
<script async="" src="https://static.codepen.io/assets/embed/ei.js"></script>
LCH colors in CSS: what, why, and how?2020-04-04T00:00:00Zhttps://lea.verou.me/?p=2934<p>I was always interested in color science. In 2014, I gave a talk about CSS Color 4 at various conferences around the world called <em><a href="https://www.youtube.com/results?search_query=the+chroma+zone+lea+verou&page=&utm_source=opensearch">“The Chroma Zone”</a></em>. Even before that, in 2009, I wrote a color picker that used a hidden Java applet to support ICC color profiles to do CMYK properly, a first on the Web at the time (to my knowledge). I never released it, but it sparked <a href="http://lea.verou.me/2009/03/100-cyan-in-cmyk-is-not-rgb0255255/">this angry rant</a>.</p>
<p>Color is also how I originally met my now husband, Chris Lilley: In my first CSS WG meeting in 2012, he approached me to ask a question about CSS and Greek, and once he introduced himself I said <em>“You’re Chris Lilley, the color expert?!? I have <strong>questions</strong> for you!”</em>. I later discovered that he had done even more cool things (he was a co-author of PNG and started SVG 🤯), but at the time, I only knew of him as “the W3C color expert”, that’s how much into color I was <em>(I got my color questions answered much later, in 2015 that we actually got together)</em>.</p>
<p>My interest in color science was renewed in 2019, after I became co-editor of <a href="http://drafts.csswg.org/css-color-5">CSS Color 5</a>, with the goal of fleshing out <a href="https://drafts.csswg.org/css-color-5/#relative-colors">my color modification proposal</a>, which aims to allow arbitrary tweaking of color channels to create color variations, and combine it with <a href="https://drafts.csswg.org/css-color-5/#coloradjust">Una’s color modification proposal</a>. LCH colors in CSS is something I’m very excited about, and I strongly believe designers would be <strong>outraged</strong> we don’t have them yet if they knew more about them.</p>
<h2 id="what-is-lch%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#what-is-lch%3F">What is LCH?</a></h2>
<p><a href="https://www.w3.org/TR/css-color-4/#specifying-lab-lch">CSS Color 4 defines lch() colors</a>, among other things, and as of recently, all major browsers have started implementing them or are seriously considering it:</p>
<ul>
<li><a href="https://bugs.webkit.org/show_bug.cgi?id=205675">Safari is already implementing</a>,</li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1026287">Chrome is about to</a>,</li>
<li>and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1352757">Firefox is discussing it</a>.</li>
</ul>
<p>LCH is a color space that has several advantages over the RGB/HSL colors we’re familiar with in CSS. In fact, I’d go as far as to call it a <strong>game-changer</strong>, and here’s why.</p>
<h3 id="1.-we-actually-get-access-to-about-50%25-more-colors." tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#1.-we-actually-get-access-to-about-50%25-more-colors.">1. <strong>We actually get access to about 50% more colors.</strong></a></h3>
<p>This is huge. Currently, every CSS color we can specify, is <a href="https://www.w3.org/TR/css-color-3/#rgb-color">defined</a> to be in the <a href="https://en.wikipedia.org/wiki/SRGB">sRGB color space</a>. This was more than sufficient a few years ago, since all but professional monitors had gamuts smaller than sRGB. However, that’s not true any more. Today, the gamut (range of possible colors displayed) of most monitors is closer to <a href="https://en.wikipedia.org/wiki/DCI-P3">P3</a>, which has a <a href="https://twitter.com/svgeesus/status/1220029106248716288">50% larger volume than sRGB</a>. CSS right now <strong>cannot access these colors at all</strong>. Let me repeat: <strong>We have no access to one third of the colors in most modern monitors.</strong> And these are not just any colors, but the <strong>most vivid colors the screen can display</strong>. Our websites are washed out because monitor hardware evolved faster than CSS specs and browser implementations.</p>
<p><img src="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/images/srgb-vs-p3.png" alt="" /></p>
<p>Gamut volume of sRGB vs P3</p>
<h3 id="2.-lch-(and-lab)-is-perceptually-uniform" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#2.-lch-(and-lab)-is-perceptually-uniform">2. LCH (and Lab) is perceptually uniform</a></h3>
<p>In LCH, the same numerical change in coordinates produces the same perceptual color difference. This property of a color space is called “perceptual uniformity”. RGB or HSL are not perceptually uniform. A very illustrative <a href="https://dabblet.com/gist/48ce387697106b845127d5cef5247a19">example</a> is the following [<a href="https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/?fbclid=IwAR0ztieCugTlncQH6FB7fqtr1NKaQjuAgwc5YzA75NPbLwH3Z5w1FBsdQQw">example source</a>]:</p>
<p><a href="https://dabblet.com/gist/48ce387697106b845127d5cef5247a19"><img src="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/images/image-3.png" alt="" /></a></p>
<p>Both the colors in the first row, as well as the colors in the second row, only differ by 20 degrees in hue. Is the perceptual difference between them equal?</p>
<h3 id="3.-lch-lightness-actually-means-something" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#3.-lch-lightness-actually-means-something">3. LCH lightness actually means something</a></h3>
<p>In HSL, lightness is meaningless. Colors can have the same lightness value, with wildly different perceptual lightness. My favorite examples are yellow and blue. Believe it or not, both have the same HSL lightness!</p>
<p><a href="https://dabblet.com/gist/a6eb208ae80780c55b443ddcd4ce842f"><img src="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/images/image-4.png" alt="" /></a></p>
<p>Both of these colors have a lightness of 50%, but they are most certainly not equally light. What does HSL lightness actually mean then?</p>
<p>You might argue that at least lightness means something for constant hue and saturation, i.e. for adjustments within the same color. It is true that we do get a lighter color if we increase the HSL lightness and a darker one if we decrease it, but it’s not necessarily the same color:</p>
<p><img src="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/images/image-5.png" alt="" /></p>
<p>Both of these have the same hue and saturation, but do they really look like darker and lighter variants of the same color?</p>
<p>With LCH, any colors with the same lightness are equally perceptually light, and any colors with the same chroma are equally perceptually saturated.</p>
<h2 id="how-does-lch-work%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#how-does-lch-work%3F">How does LCH work?</a></h2>
<p>LCH stands for “Lightness Chroma Hue”. The parameters loosely correspond to HSL’s, however there are a few crucial differences:</p>
<p><strong>The hue angles don’t fully correspond to HSL’s hues.</strong> E.g. 0 is not red, but more of a magenta and 180 is not turquoise but more of a bluish green, and is exactly complementary.</p>
<p><img src="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/images/image-8.png" alt="" /></p>
<p>Note how these colors, while wildly different in hue, perceptually have the same lightness.</p>
<p>In HSL, saturation is a neat 0-100 percentage, since it’s a simple transformation of RGB into polar coordinates. In LCH however, <strong>Chroma is theoretically unbounded</strong>. LCH (like Lab) is designed to be able to represent the entire spectrum of human vision, and not all of these colors can be displayed by a screen, even a P3 screen. Not only is the maximum chroma different depending on screen gamut, it’s actually different per color.</p>
<p>This may be better understood with an example. For simplicity, assume you have a screen whose gamut exactly matches the sRGB color space (for comparison, the screen of a 2013 MacBook Air was about 60% of sRGB, although most modern screens are about 150% of sRGB, as discussed above). For L=50 H=180 (the cyan above), the maximum Chroma is only 35! For L=50 H=0 (the magenta above), Chroma can go up to 77 without exceeding the boundaries of sRGB. For L=50 H=320 (the purple above), it can go up to 108!</p>
<p>While the lack of boundaries can be somewhat unsettling <em>(in people and in color spaces)</em>, don’t worry: if you specify a color that is not displayable in a given monitor, it will be scaled down so that it becomes visible while preserving its essence. After all, that’s not new: before monitors got gamuts wider than sRGB, this is what was happening with regular CSS colors when they were displayed in monitors with gamuts smaller than sRGB.</p>
<h2 id="an-lch-color-picker" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#an-lch-color-picker">An LCH color picker</a></h2>
<p>Hopefully, you are now somewhat excited about LCH, but how to visualize it?</p>
<p>I actually made this a while ago, primarily to help me, <a href="https://twitter.com/svgeesus">Chris</a>, <a href="https://twitter.com/argyleink">Adam</a>, and <a href="https://twitter.com/una">Una</a> in wrapping our heads around LCH sufficiently to edit <a href="https://drafts.csswg.org/css-color-5/">CSS Color 5</a>. It’s different to know the theory, and it’s different to be able to play with sliders and see the result. I even bought a domain, <a href="https://css.land/">css.land</a>, to host similar demos eventually. We used it a fair bit, and Chris got me to add a few features too, but I never really posted about it, so it was only accessible to us, and anybody that noticed <a href="https://github.com/LeaVerou/css.land/">its Github repo</a>.</p>
<p><a href="https://css.land/lch"><img src="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/images/image-1.png" alt="" /></a></p>
<p>Why not just use an existing LCH color picker?</p>
<ul>
<li>The conversion code for this is written by Chris, and he was confident the math is at least intended to be correct (i.e. if it’s wrong it’s a bug in the code, not a gap in understanding)</li>
<li>The Chroma is not 0-100 like in some color pickers we found</li>
<li>We wanted to allow inputting arbitrary CSS colors (the “Import…” button above)</li>
<li>We wanted to allow inputting decimals (the sliders only do integers, but the black number inputs allow any number)</li>
<li>I wanted to be able to store colors, and see how they interpolate.</li>
<li>We wanted to be able to see whether the LCH color was within sRGB, P3, (or Rec.2020, an even larger color space).</li>
<li>We wanted alpha</li>
<li>And lastly, because it’s fun! Especially since it’s implemented with <a href="https://mavo.io/">Mavo</a> (and a little bit of JS, this is not a pure Mavo HTML demo).</li>
</ul>
<p>Recently, Chris posted it in a <a href="https://github.com/whatwg/html/issues/3400?#issuecomment-607976086">whatwg/html issue thread</a> and many people discovered it, so it nudged me to post about it, so, here it is: <a href="https://css.land/lch">css.land/lch</a></p>
<h2 id="faq" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#faq">FAQ</a></h2>
<p>Based on the questions I got after I posted this article, I should clarify a few common misconceptions.</p>
<p><strong><em>“You said that these colors are not implemented yet, but I see them in your article”</em></strong></p>
<p>All of the colors displayed in this article are within the sRGB gamut, exactly because we can’t display those outside it yet. sRGB is a color space, not a syntax. E.g. <code>rgb(255 0 0)</code> and <code>lch(54.292% 106.839 40.853)</code> specify the same color.</p>
<p><strong><em>“How does the LCH picker display colors outside sRGB?”</em></strong></p>
<p>It doesn’t. Neither does any other on the Web (to my knowledge). The color picker is implemented with web technologies, and therefore suffers from the same issues. It has to scale them down to display something similar, that is within sRGB (it used to just clip the RGB components to 0-100%, but thanks to <a href="https://github.com/LeaVerou/css.land/pull/3">this PR</a> from Tab it now uses a far superior algorithm: it just reduces the Chroma until the color is within sRGB). <strong>This is why increasing the Chroma doesn’t produce a brighter color beyond a certain point: because that color cannot be displayed with CSS right now.</strong></p>
<p><strong><em>“I’ve noticed that Firefox displays more vivid colors than Chrome and Safari, is that related?”</em></strong></p>
<p>Firefox does not implement the spec that restricts CSS colors to sRGB. Instead, it just throws the raw RGB coordinates on the screen, so e.g. <code>rgb(100% 0% 0%)</code> is the brightest red your screen can display. While this may seem like a superior solution, it’s incredibly inconsistent: specifying a color is approximate at best, since every screen displays it differently. By restricting CSS colors to a known color space (sRGB) we gained device independence. LCH and Lab are also device independent as they are based on actual measured color.</p>
<p><strong><em>What about color(display-p3 r g b)? Safari supports that since 2017!</em></strong></p>
<p>I was notified of this after I posted this article. I was aware Safari was implementing this syntax a while ago, but somehow missed that they shipped it. In fact, WebKit published <a href="https://webkit.org/blog/10042/wide-gamut-color-in-css-with-display-p3/">an article about this syntax</a> last month! How exciting!</p>
<p><code>color(colorspaceid params)</code> is another syntax added by <a href="https://www.w3.org/TR/css-color-4/#color-function">CSS Color 4</a> and is the swiss army knife of color management in CSS: in its full glory it allows specifying an ICC color profile and colors from it (e.g. you want real CMYK colors on a webpage? You want Pantone? With color profiles, you can do that too!). It also supports some predefined color spaces, of which <code>display-p3</code> is one. So, for example, <code>color(display-p3 0 1 0)</code> gives us the brightest green in the P3 color space. You can use <a href="https://dabblet.com/gist/f491f94dba0af1dfccffa24c46e770e5">this test case</a> to test support: you’ll see red if <code>color()</code> is not supported and bright green if it is.</p>
<iframe src="https://dabblet.com/gist/f491f94dba0af1dfccffa24c46e770e5" style="width: 100%; height: 400px;"></iframe>
<p>Exciting as it may be (and I should tweak the color picker to use it when available!), do note that it only addresses the first issue I mentioned: getting to all gamut colors. However, since it’s RGB-based, it still suffers from the other issues of RGB. It is not perceptually uniform, and is difficult to create variants (lighter or darker, more or less vivid etc) by tweaking its parameters.</p>
<p>Furthermore, it’s a short-term solution. It works now, because screens that can display a wider gamut than P3 are rare. Once hardware advances again, <code>color(display-p3 ...)</code> will have the same problem as sRGB colors have today. LCH and Lab are device independent, and can represent the entire gamut of human vision so they will work regardless of how hardware advances.</p>
<p><strong><em>How does LCH relate to the Lab color space that I know from Photoshop and other applications?</em></strong></p>
<p>LCH is the same color space as Lab, just viewed differently! Take a look at the following diagram that I made for my students:</p>
<p>The L in Lab and LCH is exactly the same (perceptual Lightness). For a given lightness L, in Lab, a color has cartesian coordinates (L, a, b) and polar coordinates (L, C, H). Chroma is just the length of the line from 0 to point (a, b) and Hue is the angle of that ray. Therefore, the formulae to convert Lab to LCH are trivial one liners: C is sqrt(a² + b²) and H is atan(b/a) (with different handling if a = 0). atan() is just the reverse of tan(), i.e. tan(H) = b/a.</p>
Today's Javascript, from an outsider's perspective2020-05-25T00:00:00Zhttps://lea.verou.me/?p=2969<p>Today I tried to help a friend who is a great computer scientist, but not a JS person use a JS module he found on Github. Since for the past 6 years my day job is doing usability research & teaching at MIT, I couldn’t help but cringe at the slog that this was. Lo and behold, a pile of unnecessary error conditions, cryptic errors, and lack of proper feedback. And I don’t feel I did a good job communicating the frustration he went through in the one hour or so until he gave up.</p>
<p>It went a bit like this…</p>
<p>Note: N_ames of packages and people have been changed to protect their identity. I’ve also omitted a few issues he faced that were too specific to the package at hand. Some of the errors are reconstructed from memory, so let me know if I got anything wrong!_</p>
<p><strong>John:</strong> Hey, I want to try out this algorithm I found on Github, it says to use <code>import functionName from packageName</code> and then call <code>functionName(arguments)</code>. Seems simple enough! I don’t really need a UI, so I’m gonna use Node!</p>
<p><strong>Lea:</strong> Sure, Node seems appropriate for this!</p>
<p>John <em>runs <code>npm install packageName --save</code> as recommended by the package’s README</em>
John <em>runs <code>node index.js</code></em></p>
<p><strong>Node:</strong></p>
<p>Warning: To load an ES module, set “type”: “module” in the package.json or use the .mjs extension.
SyntaxError: Cannot use import statement outside a module</p>
<p><strong>John:</strong> But I don’t have a package.json…
<strong>Lea:</strong> Run <code>npm init</code>, it will generate it for you!</p>
<p><em>John runs <code>npm init</code>, goes through the wizard, adds <code>type: "module"</code></em> manually to the generated package.json.
John <em>runs <code>node index.js</code></em></p>
<p><strong>Node:</strong></p>
<p>SyntaxError: Cannot use import statement outside a module</p>
<p>Oddly, the error was thrown from an internal module of the project this time. WAT?!</p>
<p><strong>Lea:</strong> Ok, screw this, just run it in a browser, it’s an ES6 module and it’s just a pure JS algorithm that doesn’t use any Node APIs, it should work.</p>
<p>John <em>makes a simple index.html with a <code><script type="module" src="index.js"></code></em>
John <em>loads index.html in a browser</em></p>
<p>Nothing in the console. Nada. Crickets. 🦗</p>
<p><strong>Lea:</strong> Oh, you need to adjust your module path to import packageName. Node does special stuff to resolve based on <code>node_modules</code>, now you’re in a browser you need to specify an explicit path yourself.</p>
<p><em>John looks, at his filesystem, but there was no node_modules directory.</em></p>
<p><strong>Lea:</strong> Oh, you ran <code>npm install</code> before you had a <code>package.json</code>, that’s probably it! Try it again!</p>
<p><em>John runs <code>npm install packageName --save</code> again</em></p>
<p><strong>John:</strong> Oh yeah, there is a node_modules now!</p>
<p>John <em>desperately looks in <code>node_modules</code> to find the entry point</em>
John <em>edits his index.js accordingly, reloads index.html</em></p>
<p><strong>Firefox:</strong></p>
<p>Incorrect MIME type: text/html</p>
<p><strong>Lea:</strong> Oh, you’re in <code>file://</code>! Dude, what are you doing these days without a localhost? Javascript is severely restricted in <code>file://</code> today.</p>
<p><strong>John:</strong> But why do I… ok fine, I’m going to start a localhost.</p>
<p>John <em>starts localhost</em>, visits his index.html under <a href="http://localhost/">http://localhost:80</a></p>
<p><strong>Firefox:</strong></p>
<p>Incorrect MIME type: text/html</p>
<p><strong>John:</strong> Sigh. Do I need to configure my localhost to serve JS files with a <code>text/javascript</code> MIME type?
<strong>Lea:</strong> What? No! It knows this. Um… look at the Networks tab, I suspect it can’t find your module, so it’s returning an HTML page for the 404, then it complains because the MIME type of the error page is not <code>text/javascript</code>.</p>
<p><em>Looks at node_modules again, corrects path. Turns out VS Code collapses folders with only 1 subfolder, which is why we hadn’t noticed</em>.</p>
<p>FWIW I do think this is a good usability improvement on VS Code’s behalf, it improves efficiency, but they need to make it more visible that this is what has happened.</p>
<p><strong>Firefox:</strong></p>
<p>SyntaxError: missing ) after formal parameters</p>
<p><strong>Lea:</strong> What? That’s coming from the package source, it’s not your fault. I don’t understand… can we look at this line?</p>
<p><em>John clicks at line throwing the error</em></p>
<p><strong>Lea:</strong> Oh my goodness. This is not Javascript, it’s Typescript!! With a .js extension!!
<strong>John:</strong> I just wanted to run one line of code to test this algorithm… 😭😭😭</p>
<p><em>John gives up</em>. <em>Concludes never to touch Node, npm, or ES6 modules with a barge pole.</em></p>
<p>The End.</p>
<p>Note that John is a computer scientist that knows a fair bit about the Web: He had Node & npm installed, he knew what MIME types are, he could start a localhost when needed. What hope do actual novices have?</p>
New decade, new theme2020-06-03T00:00:00Zhttps://lea.verou.me/?p=3000<p>It has been <a href="http://lea.verou.me/2011/01/yet-another-redesign/">almost a decade since this blog last saw a redesign</a>.</p>
<p><img src="https://lea.verou.me/2020/06/new-decade-new-theme/images/image.png" alt="" /></p>
<p>This blog’s theme 2011 - 2020. RIP!</p>
<p>In these 9 years, my life changed dramatically. I <a href="http://lea.verou.me/2012/08/lea-at-w3-org/">joined</a> and <a href="http://lea.verou.me/2013/07/leaving-w3c/">left W3C</a>, <a href="https://en.wikipedia.org/wiki/CSS_Working_Group">joined the CSS WG</a>, <a href="http://lea.verou.me/2014/02/im-going-to-mit/">went to MIT for a PhD</a>, <a href="http://www.amazon.com/CSS-Secrets-Lea-Verou/dp/1449372635?tag=leaverou-20">published a book</a>, <a href="https://www.facebook.com/leaverou/posts/10156857680266192">got married</a>, <a href="https://twitter.com/leaverou/status/1153045069286563841">had a baby</a>, among other things. I designed dozens of websites for dozens of projects, but this theme remained constant, with probably a hasty tweak here and there but nothing more than that. Even its mobile version was a few quick media queries to make it <em>palatable</em> on mobile.</p>
<p>To put this into perspective, when I designed that theme:</p>
<ul>
<li>CSS gradients were still cutting edge</li>
<li>We were still using browser prefixes all over the place</li>
<li>RSS was still a thing that websites advertised</li>
<li>Skeuomorphism was all the rage</li>
<li>Websites were desktop first, and often desktop-only.</li>
<li>Opera was a browser we tested in.</li>
<li>IE8 was the latest IE version. It didn’t support SVG, gradients, border-radius, shadows, web fonts (except .eot), transforms, <code><video></code>, <code><audio></code>, <code><canvas></code></li>
<li>We were still hacking layout with floats, clearfix and <code>overflow: hidden</code></li>
</ul>
<p>Over the course of these years, I kept saying “I need to update my website’s theme”, but never got around to it, there was always something more high priority.</p>
<p>The stroke that broke the camel’s back was this Monday. I came up with a nice CSS tip on another website I was working on, and realized I was hesitating to blog about it because <em>I was embarrassed</em> at how my website looked. This is it, I thought. If it has gotten so bad that I avoid blogging because I don’t want people to be reminded of how old my website looks, I need to get my shit together and fix this, I told myself.</p>
<p>My plan was to design something entirely from scratch, like I had done the previous time (the previous theme used a blank HTML5 starter theme as its only starting point). However, when I previewed the new Wordpress default (<a href="https://wordpress.org/themes/twentytwenty/">Twenty Twenty</a>), I fell in love, especially with its typography: it used <a href="https://rsms.me/inter/">a very Helvetica-esque variable font</a> as its heading typeface, and Hoefler Text for body text. 😍</p>
<p>It would surely be very convenient to be able to adapt an existing theme, but on the other hand, isn’t it embarrassing to be known for CSS and use the default theme or something close to it?</p>
<p>In the end, I kept the things I liked about it and it certainly still looks a lot like Twenty Twenty, but I think I’ve made enough tweaks that it’s also very <em>Lea</em>. And of course there are animated conic gradients in it, because duh. 😂</p>
<p>Do keep in mind that this is just a day’s work, so it will be rough around the edges and still very much a work in progress. Let me know about any issues you find in the comments!</p>
<p>PS: Yes, yes, I will eventually get around to enforcing <code>https://</code>!</p>
Hybrid positioning with CSS variables and max()2020-06-05T00:00:00Zhttps://lea.verou.me/?p=3034<p>Notice how the navigation on the left behaves wrt scrolling: It’s like absolute at first that becomes fixed once the header scrolls out of the viewport.</p>
<p>One of my side projects these days is a color space agnostic color conversion & manipulation library, which I’m developing together with my husband, <a href="https://svgees.us/">Chris Lilley</a> (you can see a sneak peek of its docs above). He brings his color science expertise to the table, and I bring my JS & API design experience, so it’s a great match and I’m really excited about it! (<em>if you’re serious about color and you’re building a tool or demo that would benefit from it contact me, we need as much early feedback on the API as we can get!</em> )</p>
<p>For the documentation, I wanted to have the page navigation on the side (when there is enough space), right under the header when scrolled all the way to the top, but I wanted it to scroll with the page (as if it was absolutely positioned) until the header is out of view, and then stay at the top for the rest of the scrolling (as if it used fixed positioning).</p>
<p>It sounds very much like a case for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position"><code>position: sticky</code></a>, doesn’t it? However, an element with <code>position: sticky</code> behaves like it’s relatively positioned when it’s in view and like it’s using <code>position: fixed</code> when its scrolled out of view but its container is still in view. What I wanted here was different. I basically wanted <code>position: absolute</code> while the header was in view and <code>position: fixed</code> after. Yes, there are ways I could have contorted <code>position: sticky</code> to do what I wanted, but was there another solution?</p>
<p>In the past, we’d just go straight to JS, slap <code>position: absolute</code> on our element, calculate the offset in a <code>scroll</code> event listener and set a <code>top</code> CSS property on our element. However, this is flimsy and violates separation of concerns, as we now need to modify Javascript to change styling. Pass!</p>
<p>What if instead we had access to the scroll offset in CSS? Would that be sufficient to solve our use case? Let’s find out!</p>
<p>As I pointed out in my <a href="https://increment.com/frontend/a-users-guide-to-css-variables/">Increment article about CSS Variables</a> last month, and in <a href="https://www.youtube.com/results?search_query=lea+verou+%22css+variables%22">my CSS Variables series of talks a few years ago</a>, we can use JS to set & update CSS variables on the root that describe pure data (mouse position, input values, scroll offset etc), and then use them as-needed throughout our CSS, reaching near-perfect separation of concerns for many common cases. In this case, we write 3 lines of JS to set a <code>--scrolltop</code> variable:</p>
<pre><code>let root = document.documentElement;
document.addEventListener("scroll", evt => {
root.style.setProperty("--scrolltop", root.scrollTop);
});
</code></pre>
<p>Then, we can position our navigation absolutely, and subtract <code>var(--scrolltop)</code> to offset any scroll (<code>11rem</code> is our header height):</p>
<pre><code>#toc {
position: fixed;
top: calc(11rem - var(--scrolltop) * 1px);
}
</code></pre>
<p>This works up to a certain point, but once scrolltop exceeds the height of the header, <code>top</code> becomes negative and our navigation starts drifting off screen:</p>
<p>Just subtracting <code>--scrolltop</code> essentially implements absolute positioning with <code>position: fixed</code>.</p>
<p>We’ve basically re-implemented absolute positioning with <code>position: fixed</code>, which is not very useful! What we <em>really</em> want is to cap the result of the calculation to <code>0</code> so that our navigation always remains visible. Wouldn’t it be great if there was a <code>max-top</code> attribute, just like <code>max-width</code> so that we could do this?</p>
<p>One thought might be to change the JS and use <code>Math.max()</code> to cap <code>--scrolltop</code> to a specific number that corresponds to our header height. However, while this would work for this particular case, it means that <code>--scrolltop</code> cannot be used generically anymore, because it’s tailored to our specific use case and does not correspond to the actual scroll offset. Also, this encodes more about styling in the JS than is ideal, since the clamping we need is presentation-related — if our style was different, we may not need it anymore. But how can we do this without resorting to JS?</p>
<p>Thankfully, we recently got implementations for probably the one feature I was pining for the most in CSS, for years: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/min"><code>min()</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max"><code>max()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clamp"><code>clamp()</code></a> functions, which bring the power of min/max constraints to any CSS property! And even for <code>width</code> and <code>height</code>, they are strictly more powerful than <code>min/max-*</code> because you can have any number of minimums and maximums, whereas the <code>min/max-*</code> properties limit you to only one.</p>
<p>While <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max#Browser_compatibility">brower compatibility is actually pretty good</a>, we can’t just use it with no fallback, since this is one of the features where lack of support can be destructive. We will provide a fallback in our base style and use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports"><code>@supports</code></a> to conditonally override it:</p>
<pre><code>#toc {
position: fixed;
top: 11em;
}
@supports (top: max(1em, 1px)) {
#toc {
top: max(0em, 11rem - var(--scrolltop) * 1px);
}
}
</code></pre>
<p>Aaand that was it, this gives us the result we wanted!</p>
<p>And because <code>--scrolltop</code> is sufficiently generic, we can re-use it anywhere in our CSS where we need access to the scroll offset. I’ve actually used exactly the scame <code>--scrolltop</code> setting JS code in my blog, to keep the gradient centerpoint on my logo while maintaining a <code>fixed</code> background attachment, so that various elements can use the same background and having it appear continuous, i.e. not affected by their own background positioning area:</p>
<p>The website header and the post header are actually different element. The background appears continuous because it’s using <code>background-attachment: fixed</code>, and the scrolltop variable is used to emulate <code>background-attachment: scroll</code> while still using the viewport as the background positioning area for both backgrounds.</p>
<h3 id="appendix%3A-why-didn%E2%80%99t-we-just-use-the-cascade%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/hybrid-positioning-with-css-variables-and-max/#appendix%3A-why-didn%E2%80%99t-we-just-use-the-cascade%3F">Appendix: Why didn’t we just use the cascade?</a></h3>
<p>You might wonder, why do we even need <code>@supports</code>? Why not use the cascade, like we’ve always done to provide fallbacks for values without sufficiently universal support? I.e., why not just do this:</p>
<pre><code>#toc {
position: fixed;
top: 11em;
top: max(0em, 11rem - var(--scrolltop) * 1px);
}
</code></pre>
<p>The reason is that when you use CSS variables, this does not work as expected. The browser doesn’t know if your property value is valid until the variable is resolved, and by then it has already processed the cascade and has thrown away any potential fallbacks.</p>
<p>So, what would happen if we went this route and <code>max()</code> was not supported? Once the browser realizes that the second value is invalid due to using an unknown function, it will make the property <em><a href="https://www.w3.org/TR/css-variables-1/#invalid-at-computed-value-time">invalid at computed value time</a></em>, which essentially equates to the <code>initial</code> keyword, and for the <code>top</code> property, the initial value is <code>0</code>. This would mean your navigation would overlap the header when scrolled close to the top, which is <em>terrible</em>!</p>
Refactoring optional chaining into a large codebase: lessons learned2020-06-18T00:00:00Zhttps://lea.verou.me/?p=3054<p><a href="https://coink.wang/refactoring-optional-chaining-into-a-large-codebase-lessons-learned-chinese.html">Chinese translation by Coink Wang</a></p>
<p><img src="https://memegenerator.net/img/instances/400x/80477833/refactor-all-the-things.jpg" alt="" /></p>
<p>Now that <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining">optional chaining</a> is supported <a href="https://caniuse.com/#feat=mdn-javascript_operators_optional_chaining">across the board</a>, I decided to finally refactor <a href="https://mavo.io/">Mavo</a> to use it (yes, yes, we do provide a transpiled version as well for older browsers, settle down). This is a moment I have been waiting for a long time, as I think optional chaining is the single most substantial JS syntax improvement since arrow functions and template strings. Yes, I think it’s more significant than async/await, just because of the mere frequency of code it improves. Property access is literally <em>everywhere</em>.</p>
<p>First off, what is <em>optional chaining</em>, in case you haven’t heard of it before?</p>
<p>You know how you can’t just do <code>foo.bar.baz()</code> without checking if <code>foo</code> exists, and then if <code>foo.bar</code> exists, and then if <code>foo.bar.baz</code> exists because you’ll get an error? So you have to do something awkward like:</p>
<pre><code>if (foo && foo.bar && foo.bar.baz) {
foo.bar.baz();
}
</code></pre>
<p>Or even:</p>
<pre><code>foo && foo.bar && foo.bar.baz && foo.bar.baz();
</code></pre>
<p>Some even <a href="https://medium.com/@ismail9k/use-javascript-optional-chaining-today-f0b1d080b3c6">contort object destructuring to help with this</a>. With optional chaining, you can just do this:</p>
<pre><code>foo?.bar?.baz?.()
</code></pre>
<p>It supports normal property access, brackets (<code>foo?.[bar]</code>), and even function invocation (<code>foo?.()</code>). Sweet, right??</p>
<p>Yes, <em>mostly.</em> Indeed, there is SO MUCH code that can be simplified with it, it’s incredible. But there are a few caveats.</p>
<h2 id="patterns-to-search-for" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#patterns-to-search-for">Patterns to search for</a></h2>
<p>Suppose you decided to go ahead and refactor your code as well. What to look for?</p>
<p>There is of course the obvious <code>foo && foo.bar</code> that becomes <code>foo?.bar</code>.</p>
<p>There is also the conditional version of it, that we described in the beginning of this article, which uses <code>if()</code> for some or all of the checks in the chain.</p>
<p>There are also a few more patterns.</p>
<h3 id="ternary" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#ternary">Ternary</a></h3>
<pre><code>foo? foo.bar : defaultValue
</code></pre>
<p>Which can now be written as:</p>
<pre><code>foo?.bar || defaultValue
</code></pre>
<p>or, using the other awesome new operator, the <em><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator">nullish coalescing operator</a></em>:</p>
<pre><code>foo?.bar ?? defaultValue
</code></pre>
<h3 id="array-checking" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#array-checking">Array checking</a></h3>
<pre><code>if (foo.length > 3) {
foo[2]
}
</code></pre>
<p>which now becomes:</p>
<pre><code>foo?.[2]
</code></pre>
<p>Note that this is no substitute for a real array check, like the one done by <code>Array.isArray(foo)</code>. Do not go about replacing proper array checking with duck typing because it’s shorter. We stopped doing that over <a href="http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/">a decade ago</a>.</p>
<h3 id="regex-match" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#regex-match">Regex match</a></h3>
<p>Forget about things like this:</p>
<pre><code>let match = "#C0FFEE".match(/#([A-Z]+)/i);
let hex = match && match[1];
</code></pre>
<p>Or even things like that:</p>
<pre><code>let hex = ("#C0FFEE".match(/#([A-Z]+)/i) || [,])[1];
</code></pre>
<p>Now it’s just:</p>
<pre><code>let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1];
</code></pre>
<p>In our case, I was able to even remove two utility functions and replace their invocations with this.</p>
<h3 id="feature-detection" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#feature-detection">Feature detection</a></h3>
<p>In simple cases, feature detection can be replaced by <code>?.</code>. For example:</p>
<pre><code>if (element.prepend) element.prepend(otherElement);
</code></pre>
<p>becomes:</p>
<pre><code>element.prepend?.(otherElement);
</code></pre>
<h3 id="don%E2%80%99t-overdo-it" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#don%E2%80%99t-overdo-it">Don’t overdo it</a></h3>
<p>While it may be tempting to convert code like this:</p>
<pre><code>if (foo) {
something(foo.bar);
somethingElse(foo.baz);
andOneLastThing(foo.yolo);
}
</code></pre>
<p>to this:</p>
<pre><code>something(foo?.bar);
somethingElse(foo?.baz);
andOneLastThing(foo?.yolo);
</code></pre>
<p><em>Don’t</em>. You’re essentially having the JS runtime check <code>foo</code> three times instead of one. You may argue these things don’t matter much anymore performance-wise, but it’s the same repetition for the human reading your code: they have to mentally process the check for <code>foo</code> three times instead of one. And if they need to add another statement using property access on <code>foo</code>, they need to add yet another check, instead of just using the conditional that’s already there.</p>
<h2 id="caveats" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#caveats">Caveats</a></h2>
<h3 id="you-still-need-to-check-before-assignment" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#you-still-need-to-check-before-assignment">You still need to check before assignment</a></h3>
<p>You may be tempted to convert things like:</p>
<pre><code>if (foo && foo.bar) {
foo.bar.baz = someValue;
}
</code></pre>
<p>to:</p>
<pre><code>foo?.bar?.baz = someValue;
</code></pre>
<p>Unfortunately, that’s not possible and will error. This was an actual snippet from our codebase:</p>
<pre><code>if (this.bar && this.bar.edit) {
this.bar.edit.textContent = this._("edit");
}
</code></pre>
<p>Which I happily refactored to:</p>
<pre><code>if (this.bar?.edit) {
this.bar.edit.textContent = this._("edit");
}
</code></pre>
<p>All good so far, this works nicely. But then I thought, wait a second… do I need the conditional at all? Maybe I can just do this:</p>
<pre><code>this.bar?.edit?.textContent = this._("edit");
</code></pre>
<p>Nope. <code>Uncaught SyntaxError: Invalid left-hand side in assignment</code>. Can’t do that. You still need the conditional. I literally kept doing this, and I’m glad I had ESLint in my editor to warn me about it without having to actually run the code.</p>
<h3 id="it%E2%80%99s-very-easy-to-put-the-%3F.-in-the-wrong-place-or-forget-some-%3F." tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#it%E2%80%99s-very-easy-to-put-the-%3F.-in-the-wrong-place-or-forget-some-%3F.">It’s very easy to put the ?. in the wrong place or forget some ?.</a></h3>
<p>Note that if you’re refactoring a long chain with optional chaining, you often need to insert multiple <code>?.</code> after the first one, for every member access that may or may not exist, otherwise you will get errors once the optional chaining returns undefined.</p>
<p>Or, sometimes you may <em>think</em> you do, because you put the <code>?.</code> in the wrong place.</p>
<p>Take the following real example. I originally refactored this:</p>
<pre><code>this.children[index]? this.children[index].element : this.marker
</code></pre>
<p>into this:</p>
<pre><code>this.children?.[index].element ?? this.marker
</code></pre>
<p>then got a <code>TypeError: Cannot read property 'element' of undefined</code>. Oops! Then I fixed it by adding an additional <code>?.</code>:</p>
<pre><code>this.children?.[index]?.element ?? this.marker
</code></pre>
<p>This works, but is superfluous, as pointed out in the comments. I just needed to <em>move</em> the <code>?.</code>:</p>
<pre><code>this.children.[index]?.element ?? this.marker
</code></pre>
<p>Note that <a href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#comment-4963612068">as pointed out in the comments</a> be careful about replacing array length checks with optional access to the index. This might be bad for performance, because out-of-bounds access on an array is de-optimizing the code in V8 (as it has to check the prototype chain for such a property too, not only decide that there is no such index in the array).</p>
<h3 id="it-can-introduce-bugs-if-you%E2%80%99re-not-careful" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#it-can-introduce-bugs-if-you%E2%80%99re-not-careful">It can introduce bugs if you’re not careful</a></h3>
<p>If, like me, you go on a refactoring spree, it’s easy after a certain point to just introduce optional chaining in places where it actually ends up <em>changing what your code does</em> and introducing subtle bugs.</p>
<h4 id="null-vs-undefined" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#null-vs-undefined">null vs undefined</a></h4>
<p>Possibly the most common pattern is replacing <code>foo && foo.bar</code> with <code>foo?.bar</code>. While in most cases these work equivalently, this is not true for every case. When <code>foo</code> is <code>null</code>, the former returns <code>null</code>, whereas the latter returns <code>undefined</code>. This can cause bugs to creep up in cases where the distinction matters and is probably the most common way to introduce bugs with this type of refactoring.</p>
<h4 id="equality-checks" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#equality-checks">Equality checks</a></h4>
<p>Be careful about converting code like this:</p>
<pre><code>if (foo && bar && foo.prop1 === bar.prop2) { /* ... */ }
</code></pre>
<p>to code like this:</p>
<pre><code>if (foo?.prop1 === bar?.prop2) { /* ... */ }
</code></pre>
<p>In the first case, the condition will not be true, unless <em>both</em> <code>foo</code> and <code>bar</code> are truthy. However, in the second case, if <em>both</em> <code>foo</code> and <code>bar</code> are nullish, the conditional will be true, because both operands will return <code>undefined</code>!</p>
<p>The same bug can creep in even if the second operand doesn’t include any optional chaining, as long as it could be <code>undefined</code> you can get unintended matches.</p>
<h4 id="operator-precedence-slips" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#operator-precedence-slips">Operator precedence slips</a></h4>
<p>One thing to look out for is that <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence">optional chaining has higher precedence than <code>&&</code></a>. This becomes particularly significant when you replace an expression using <code>&&</code> that also involves equality checks, since the (in)equality operators are sandwiched between <code>?.</code> and <code>&&</code>, having lower precedence than the former and higher than the latter.</p>
<pre><code>if (foo && foo.bar === baz) { /* ... */ }
</code></pre>
<p>What is compared with <code>baz</code> here? <code>foo.bar</code> or <code>foo && foo.bar</code>? Since <code>&&</code> has lower precedence than <code>===</code>, it’s as if we had written:</p>
<pre><code>if (foo && (foo.bar === baz)) { /* ... */ }
</code></pre>
<p>Note that the conditional cannot ever be executed if <code>foo</code> is falsy. However, once we refactor it to use optional chaining, it is now as if we were comparing (<code>foo && foo.bar</code>) to <code>baz</code>:</p>
<pre><code>if (foo?.bar === baz) { /* ... */ }
</code></pre>
<p>An obvious case where the different semantics affect execution is when <code>baz</code> is <code>undefined</code>. In that case, we can enter the conditional when <code>foo</code> is nullish, since then optional chaining will return <code>undefined</code>, which is basically the case we described above. In most other cases this doesn’t make a big difference. It can however be pretty bad when instead of an equality operator, you have an <em>inequality</em> operator, which still has the same precedence. Compare this:</p>
<pre><code>if (foo && foo.bar !== baz) { /* ... */ }
</code></pre>
<p>with this:</p>
<pre><code>if (foo?.bar !== baz) { /* ... */ }
</code></pre>
<p>Now, we are going to enter the conditional every time <code>foo</code> is nullish, as long as <code>baz</code> is not <code>undefined</code>! The difference is not noticeable in an edge case anymore, but in the average case! 😱</p>
<h4 id="return-statements" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#return-statements">Return statements</a></h4>
<p>Rather obvious after you think about it, but it’s easy to forget return statements in the heat of the moment. You cannot replace things like this:</p>
<pre><code>if (foo && foo.bar) {
return foo.bar();
}
</code></pre>
<p>with:</p>
<pre><code>return foo?.bar?.();
</code></pre>
<p>In the first case, you return conditionally, whereas in the second case you return always. This will not introduce any issues if the conditional is the last statement in your function, but it will change the control flow if it’s not.</p>
<h4 id="sometimes%2C-it-can-fix-bugs-too!" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#sometimes%2C-it-can-fix-bugs-too!">Sometimes, it can fix bugs too!</a></h4>
<p>Take a look at this code I encountered during my refactoring:</p>
<pre><code>/**
* Get the current value of a CSS property on an element
*/
getStyle: (element, property) => {
if (element) {
var value = getComputedStyle(element).getPropertyValue(property);
if (value) {
return value.trim();
}
}
},
</code></pre>
<p>Can you spot the bug? If <code>value</code> is an empty string (and given the context, it could very well be), the function will return <code>undefined</code>, because an empty string is falsy! Rewriting it to use optional chaining fixes this:</p>
<pre><code>if (element) {
var value = getComputedStyle(element).getPropertyValue(property);
return value?.trim();
}
</code></pre>
<p>Now, if <code>value</code> is the empty string, it will still return an empty string and it will only return <code>undefined</code> when <code>value</code> is nullish.</p>
<h4 id="finding-usages-becomes-trickier" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#finding-usages-becomes-trickier">Finding usages becomes trickier</a></h4>
<p>This was pointed out by Razvan Caliman on Twitter:</p>
<p><a href="https://twitter.com/razvancaliman/status/1273638529399230464">https://twitter.com/razvancaliman/status/1273638529399230464</a></p>
<h2 id="bottom-line" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/#bottom-line">Bottom line</a></h2>
<p>In the end, this refactor made Mavo about 2KB lighter and saved 37 lines of code. It did however make the transpiled version 79 lines and 9KB (!) heavier.</p>
<p><a href="https://github.com/mavoweb/mavo/commit/a8fb2e1f8c478aa7110aaf13ade57a40825ec71e">Here is the relevant commit</a>, for your perusal. I tried my best to exercise restraint and not introduce any unrelated refactoring in this commit, so that the diff is chock-full of optional chaining examples. It has 104 additions and 141 deletions, so I’d wager it has about 100 examples of optional chaining in practice. Hope it’s helpful!</p>
The Cicada Principle, revisited with CSS variables2020-07-07T00:00:00Zhttps://lea.verou.me/?p=3066<p>Many of today’s web crafters were not writing CSS at the time Alex Walker’s landmark article <a href="https://www.sitepoint.com/the-cicada-principle-and-why-it-matters-to-web-designers/">The Cicada Principle and Why it Matters to Web Designers</a> was published in 2011. Last I heard of it was in 2016, when it was <a href="https://css-tricks.com/cicada-principle-css/">used in conjunction with blend modes</a> to pseudo-randomize backgrounds even further.</p>
<p>So what <em>is</em> the Cicada Principle and how does it relate to web design in a nutshell? It boils down to: when using repeating elements (tiled backgrounds, different effects on multiple elements etc), using prime numbers for the size of the repeating unit maximizes the <em>appearance</em> of organic randomness. Note that this only works when the parameters you set are independent.</p>
<p>When I <a href="https://lea.verou.me/2020/06/new-decade-new-theme/">recently redesigned my blog</a>, I ended up using a variation of the Cicada principle to pseudo-randomize the angles of code snippets. I didn’t think much of it until I saw <a href="https://twitter.com/StuRobson/status/1273904521132072960">this tweet</a>:</p>
<p><a href="https://twitter.com/StuRobson/status/1273904521132072960">https://twitter.com/StuRobson/status/1273904521132072960</a></p>
<p>This made me think: hey, maybe I should actually write a blog post about the technique. After all, the technique itself is useful for way more than angles on code snippets.</p>
<p>The main idea is simple: You write your main rule using CSS variables, and then use <code>:nth-of-*()</code> rules to set these variables to something different every N items. If you use enough variables, and choose your Ns for them to be prime numbers, you reach a good appearance of pseudo-randomness with relatively small Ns.</p>
<p>In the case of code samples, I only have two different top cuts (going up or going down) and two different bottom cuts (same), which produce 2*2 = 4 different shapes. Since I only had four shapes, I wanted to maximize the pseudo-randomness of their order. A first attempt looks like this:</p>
<pre><code>pre {
clip-path: polygon(var(--clip-top), var(--clip-bottom));
--clip-top: 0 0, 100% 2em;
--clip-bottom: 100% calc(100% - 1.5em), 0 100%;
}
pre:nth-of-type(odd) {
--clip-top: 0 2em, 100% 0;
}
pre:nth-of-type(3n + 1) {
--clip-bottom: 100% 100%, 0 calc(100% - 1.5em);
}
</code></pre>
<p>This way, the exact sequence of shapes repeats every 2 * 3 = 6 code snippets. Also, the alternative <code>--clip-bottom</code> doesn’t really get the same visibility as the others, being present only 33.333% of the time. However, if we just add one more selector:</p>
<pre><code>pre {
clip-path: polygon(var(--clip-top), var(--clip-bottom));
--clip-top: 0 0, 100% 2em;
--clip-bottom: 100% calc(100% - 1.5em), 0 100%;
}
pre:nth-of-type(odd) {
--clip-top: 0 2em, 100% 0;
}
pre:nth-of-type(3n + 1),
pre:nth-of-type(5n + 1) {
--clip-bottom: 100% 100%, 0 calc(100% - 1.5em);
}
</code></pre>
<p>Now the exact same sequence of shapes repeats every 2 * 3 * 5 = 30 code snippets, probably way more than I will have in any article. And it’s more fair to the alternate <code>--clip-bottom</code>, which now gets 1/3 + 1/5 - 1/15 = 46.67%, which is almost as much as the alternate <code>--clip-top</code> gets!</p>
<p>You can explore this effect in <a href="https://codepen.io/leaverou/pen/8541bfd3a42551f8845d668f29596ef9?editors=1100">this codepen</a>:</p>
<p><a href="https://codepen.io/leaverou/pen/8541bfd3a42551f8845d668f29596ef9?editors=1100">https://codepen.io/leaverou/pen/8541bfd3a42551f8845d668f29596ef9?editors=1100</a></p>
<p>Or, to better explore how different CSS creates different pseudo-randomness, you can use <a href="https://codepen.io/leaverou/pen/NWxaPVx">this content-less version</a> with three variations:</p>
<p><a href="https://codepen.io/leaverou/pen/NWxaPVx">https://codepen.io/leaverou/pen/NWxaPVx</a></p>
<p>Of course, the illusion of randomness is much better with more shapes, e.g. if <a href="https://codepen.io/leaverou/pen/dyGmbJJ?editors=1100">we introduce a third type of edge</a> we get 3 * 3 = 9 possible shapes:</p>
<p><a href="https://codepen.io/leaverou/pen/dyGmbJJ?editors=1100">https://codepen.io/leaverou/pen/dyGmbJJ?editors=1100</a></p>
<p>I also used primes 7 and 11, so that the sequence repeats every 77 items. In general, the larger primes you use, the better the illusion of randomness, but you need to include more selectors, which can get tedious.</p>
<h2 id="other-examples" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/the-cicada-principle-revisited-with-css-variables/#other-examples">Other examples</a></h2>
<p>So this got me thinking: What else would this technique be cool on? Especially if we include more values as well, we can pseudo-randomize the result itself better, and not just the order of only 4 different results.</p>
<p>So I did a few experiments.</p>
<h4 id="pseudo-randomized-color-swatches" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/the-cicada-principle-revisited-with-css-variables/#pseudo-randomized-color-swatches"><a href="https://codepen.io/leaverou/pen/RwrLPer">Pseudo-randomized color swatches</a></a></h4>
<p><a href="https://codepen.io/leaverou/pen/NWxXQKX">https://codepen.io/leaverou/pen/NWxXQKX</a></p>
<p>Pseudo-randomized color swatches, with variables for hue, saturation, and lightness.</p>
<p>And <a href="https://codepen.io/leaverou/pen/RwrLPer">an alternative version</a>:</p>
<p><a href="https://codepen.io/leaverou/pen/RwrLPer">https://codepen.io/leaverou/pen/RwrLPer</a></p>
<p>Which one looks more random? Why do you think that is?</p>
<h4 id="pseudo-randomized-border-radius" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/the-cicada-principle-revisited-with-css-variables/#pseudo-randomized-border-radius"><a href="https://codepen.io/leaverou/pen/ZEQXOrd">Pseudo-randomized border-radius</a></a></h4>
<p>Admittedly, this one can be done with just longhands, but since I realized this after I had already made it, I figured eh, I may as well include it 🤷🏽♀️</p>
<p><a href="https://codepen.io/leaverou/pen/ZEQXOrd">https://codepen.io/leaverou/pen/ZEQXOrd</a></p>
<p>It is also really cool when <a href="https://codepen.io/leaverou/pen/oNbGzeE">combined with pseudo-random colors</a> (just hue this time):</p>
<p><a href="https://codepen.io/leaverou/pen/oNbGzeE">https://codepen.io/leaverou/pen/oNbGzeE</a></p>
<h4 id="pseudo-randomized-snowfall" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/the-cicada-principle-revisited-with-css-variables/#pseudo-randomized-snowfall"><a href="https://codepen.io/leaverou/pen/YzwrWvV?editors=1100">Pseudo-randomized snowfall</a></a></h4>
<p>Lots of things here:</p>
<ul>
<li>Using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/translate">translate</a> and transform together to animate them separately without resorting to <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS/RegisterProperty">CSS.registerPropery()</a></li>
<li>Pseudo-randomized horizontal offset, animation-delay, font-size</li>
<li>Technically we don’t need CSS variables to pseudo-randomize <code>font-size</code>, we can just set the property itself. However, variables enable us to pseudo-randomize it via a multiplier, in order to decouple the base font size from the pseudo-randomness, so we can edit them independently. And then we can use the same multiplier in <code>animation-duration</code> to make smaller snowflakes fall slower!</li>
</ul>
<p><a href="https://codepen.io/leaverou/pen/YzwrWvV?editors=1100">https://codepen.io/leaverou/pen/YzwrWvV?editors=1100</a></p>
<h4 id="conclusions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/the-cicada-principle-revisited-with-css-variables/#conclusions">Conclusions</a></h4>
<p>In general, the larger the primes you use, the better the illusion of randomness. With smaller primes, you will get more variation, but less appearance of randomness.</p>
<p>There are two main ways to use primes to create the illusion of randomness with <code>:nth-child()</code> selectors:</p>
<p>The first way is to set each trait on <code>:nth-child(pn + b)</code> where p is a prime that increases with each value and b is constant for each trait, like so:</p>
<pre><code>:nth-child(3n + 1) { property1: value11; }
:nth-child(5n + 1) { property1: value12; }
:nth-child(7n + 1) { property1: value13; }
:nth-child(11n + 1) { property1: value14; }
...
:nth-child(3n + 2) { property2: value21; }
:nth-child(5n + 2) { property2: value22; }
:nth-child(7n + 2) { property2: value23; }
:nth-child(11n + 2) { property2: value24; }
...
</code></pre>
<p>The benefit of this approach is that you can have as few or as many values as you like. The drawback is that because primes are sparse, and become sparser as we go, you will have a lot of “holes” where your base value is applied.</p>
<p>The second way (which is more on par with the original Cicada principle) is to set each trait on <code>:nth-child(pn + b)</code> where p is constant per trait, and b increases with each value:</p>
<pre><code>:nth-child(5n + 1) { property1: value11; }
:nth-child(5n + 2) { property1: value12; }
:nth-child(5n + 3) { property1: value13; }
:nth-child(5n + 4) { property1: value14; }
...
:nth-child(7n + 1) { property2: value21; }
:nth-child(7n + 2) { property2: value22; }
:nth-child(7n + 3) { property2: value23; }
:nth-child(7n + 4) { property2: value24; }
...
</code></pre>
<p>This creates a better overall impression of randomness (especially if you order the values in a pseudo-random way too) without “holes”, but is more tedious, as you need as many values as the prime you’re using.</p>
<p>What other cool examples can you think of?</p>
Releasing MaVoice: A free app to vote on repo issues2020-07-11T00:00:00Zhttps://lea.verou.me/?p=3081<p>First off, some news: <a href="https://github.com/HTTPArchive/almanac.httparchive.org/issues/898">I agreed to be this year’s CSS content lead for the Web Almanac</a>! One of the first things to do is to flesh out what statistics we should study to answer the question <em>“What is the state of CSS in 2020?”</em>. You can see <a href="https://almanac.httparchive.org/en/2019/css">last year’s chapter</a> to get an idea of what kind of statistics could help answer that question.</p>
<p>Of course, my first thought was <em>“We should involve the community! People might have great ideas of statistics we could study!”</em>. But what should we use to vote on ideas and make them rise to the top?</p>
<p>I wanted to use <a href="https://github.com/LeaVerou/css-almanac">a repo</a> to manage all this, since I like all the conveniences for managing issues. However, there is not much on Github for voting. You can add 👍 reactions, but not sort by them, and voting itself is tedious: you need to open the comment, click on the reaction, then go back to the list of issues, rinse and repeat. Ideally, I wanted something like UserVoice™️, which lets you vote with one click, and sorts proposals by votes.</p>
<p>And then it dawned on me: I’ll just build a <a href="https://mavo.io/">Mavo</a> app on top of the repo issues, that displays them as proposals to be voted on and sorts by 👍 reactions, UserVoice™️-style but without the UserVoice™️ price tag. 😎 In fact, I had started such a Mavo app a couple years ago, and never finished or released it. So, I just dug it up and resurrected it from its ashes! It’s — quite fittingly I think — called <em>MaVoice</em>.</p>
<p><a href="https://projects.verou.me/mavoice"><img src="https://lea.verou.me/2020/07/releasing-mavoice-a-free-app-to-vote-on-repo-issues/images/image.png" alt="" /></a></p>
<p><strong>You can set it to any repo via the <code>repo</code> URL parameter, and any label via the <code>labels</code> URL param</strong> (defaults to <code>enhancement</code>) <strong>to create a customized URL for any repo you want in seconds!</strong> For example, here’s the URL for the css-almanac repo, which only displays issues with the label “proposed stat”: <a href="https://projects.verou.me/mavoice/?repo=leaverou/css-almanac&labels=proposed%20stat">https://projects.verou.me/mavoice/?repo=leaverou/css-almanac&labels=proposed%20stat</a></p>
<p>While this did need some custom JS, unlike other Mavo apps which need none, I’m still pretty happy I could spin up this kind of app with <a href="https://github.com/LeaVerou/mavoice/blob/master/mavoice.js">< 100 lines of JS</a> :)</p>
<p>Yes, it’s still rough around the edges, and I’m sure you can find many things that could be improved, but it does the job for now, and PRs are always welcome 🤷🏽♀️</p>
<p><strong>The main caveat if you decide to use this for your own repo</strong>: Because (to my knowledge) Github API still does not provide a way to sort issues by 👍 reactions, or even reactions in general (in either the v3 REST API, or the GraphQL API), issues are instead requested sorted by comment count, and are <a href="https://github.com/LeaVerou/mavoice/blob/master/mavoice.js#L42">sorted by 👍 reactions client-side, right before render</a>. Due to API limitations, <a href="https://github.com/LeaVerou/mavoice/blob/master/index.html#L14">this API call</a> <a href="https://developer.github.com/v3/#pagination">can only fetch the top 100 results</a>. This means that if you have more than 100 issues to display (i.e. more than 100 open issues with the given label), it could potentially be inaccurate, especially if you have issues with many reactions and few comments.</p>
<p>Another caveat is that <strong>because this is basically reactions on Github issues, there is no limit on how many issues someone can vote on</strong>. In theory, if they’re a bad actor (or just overexcited), they can just vote on everything. But I suppose that’s an intrinsic problem with using reactions to vote for things, having a UI for it just reveals the existing issue, it doesn’t create it.</p>
<p>Hope you enjoy, and don’t forget to <a href="https://projects.verou.me/mavoice/?repo=leaverou/css-almanac&labels=proposed%20stat">vote on which CSS stats we should study</a>!</p>
Import non-ESM libraries in ES Modules, with client-side vanilla JS2020-07-20T00:00:00Zhttps://lea.verou.me/?p=3092<p>In case you haven’t heard, <a href="https://caniuse.com/#search=modules">ECMAScript modules (ESM) are now supported everywhere</a>!</p>
<p>While I do have <a href="https://lea.verou.me/2020/07/import-non-esm-libraries-in-es-modules-with-client-side-vanilla-js/#gripes">some gripes with them</a>, it’s too late for any of these things to change, so I’m embracing the good parts and have cautiously started using them in new projects. I do quite like that I can just use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import"><code>import</code> statements and dynamic <code>import()</code></a> for dependencies with URLs right from my JS, without module loaders, extra <code><script></code> tags in my HTML, or hacks with dynamic <code><script></code> tags and <code>load</code> events (in fact, <a href="https://blissfuljs.com/docs.html#fn-include">Bliss has had a helper for this very thing</a> that I’ve used extensively in older projects). I love that I don’t need any libraries for this, and I can use it client-side, anywhere, even in my codepens.</p>
<p>Once you start using ESM, you realize that most libraries out there are not written in ESM, nor do they include ESM builds. Many are still using globals, and those that target Node.js use CommonJS (CJS). What can we do in that case? Unfortunately, ES Modules are not really designed with any import <em>(pun intended)</em> mechanism for these syntaxes, but, there are some strategies we could employ.</p>
<h2 id="libraries-using-globals" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/import-non-esm-libraries-in-es-modules-with-client-side-vanilla-js/#libraries-using-globals">Libraries using globals</a></h2>
<p><strong>Technically, a JS file can be parsed as a module even with no imports or exports.</strong> Therefore, almost any library that uses globals can be fair game, it can just be imported as a module with no exports! How do we do that?</p>
<p>While you may not see this syntax a lot, you don’t actually need to name anything in the <code>import</code> statement. There is a syntax to import a module entirely for its side effects:</p>
<pre><code>import "url/to/library.js";
</code></pre>
<p>This syntax works fine for libraries that use globals, since declaring a global is essentially a side effect, and all modules share the same global scope. For this to work, the imported library needs to satisfy the following conditions:</p>
<ul>
<li>It should declare the global as a property on <code>window</code> (or <code>self</code>), not via <code>var Foo</code> or <code>this</code>. In modules top-level variables are local to the module scope, and <code>this</code> is <code>undefined</code>, so the last two ways would not work.</li>
<li>Its code should not violate <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode">strict mode</a></li>
<li>The URL is either same-origin or CORS-enabled. While <code><script></code> can run cross-origin resources, <code>import</code> sadly cannot.</li>
</ul>
<p>Basically, you are running a library as a module that was never written with the intention to be run as a module. Many are written in a way that also works in a module context, but not all. <a href="https://exploringjs.com/es6/ch_modules.html#_browsers-scripts-versus-modules">ExploringJS has an excellent summary of the differences between the two</a>. For example, here is <a href="https://codepen.io/leaverou/pen/dyGQXOo?editors=0011">a trivial codepen loading jQuery</a> via this method.</p>
<h2 id="libraries-using-cjs-without-dependencies" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/import-non-esm-libraries-in-es-modules-with-client-side-vanilla-js/#libraries-using-cjs-without-dependencies">Libraries using CJS without dependencies</a></h2>
<p>I dealt with this today, and it’s what prompted this post. I was trying to play around with <a href="https://github.com/reworkcss/css">Rework CSS</a>, a CSS parser used by the <a href="https://httparchive.org/">HTTPArchive</a> for analyzing CSS in the wild. However, all its code and documentation assumes Node.js. If I could avoid it, I’d really rather not have to make a Node.js app to try this out, or have to dive in module loaders to be able to require CJS modules in the browser. Was there anything I could do to just run this in a codepen, no strings attached?</p>
<p>After a little googling, I found <a href="https://github.com/reworkcss/css/issues/117">this issue</a>. So there was a JS file I could import and get all the parser functionality. Except …there was one little problem. When you look <a href="https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js">at the source</a>, it uses <code>module.exports</code>. If you just <code>import</code> that file, you predictably get an error that <code>module</code> is not defined, not to mention there are no ESM exports.</p>
<p>My first thought was to stub <code>module</code> as a global variable, import this as a module, and then read <code>module.exports</code> and give it a proper name:</p>
<pre><code>window.module = {};
import "https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js";
console.log(module.exports);
</code></pre>
<p>However, I was still getting the error that <code>module</code> was not defined. How was that possible?! They all share the same global context!! <em>*pulls hair out*</em> After some debugging, it dawned on me: <a href="https://exploringjs.com/es6/ch_modules.html#_imports-are-hoisted">static <code>import</code> statements are hoisted</a>; the “module” was getting executed before the code that imports it and stubs <code>module</code>.</p>
<p>Dynamic imports to the rescue! <code>import()</code> is executed exactly where it’s called, and returns a promise. So this actually works:</p>
<pre><code>window.module = {};
import("https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js").then(_ => {
console.log(module.exports);
});
</code></pre>
<p>We could even turn it into a wee function, which I cheekily called <code>require()</code>:</p>
<pre><code>async function require(path) {
let _module = window.module;
window.module = {};
await import(path);
let exports = module.exports;
window.module = _module; // restore global
return exports;
}
(async () => { // top-level await cannot come soon enough…
let parse = await require("https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js");
console.log(parse("body { color: red }"));
})();
</code></pre>
<p>You can fiddle with this code in a <a href="https://codepen.io/leaverou/pen/jOWQMzN?editors=0011">live pen here</a>.</p>
<p>Do note that this technique will only work if the module you’re importing doesn’t import other CJS modules. If it does, you’d need a more elaborate <code>require()</code> function, which is left as an exercise for the reader. Also, just like the previous technique, the code needs to comply with strict mode and not be cross-origin.</p>
<p>A similar technique can be used to load AMD modules via <code>import()</code>, just stub <code>define()</code> and you’re good to go.</p>
<p>So, with this technique I was able to quickly whip up a <a href="https://codepen.io/leaverou/pen/qBbQdGG">ReworkCSS playground</a>. You just edit the CSS in CodePen and see the resulting <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">AST</a>, and you can even fork it to share a specific AST with others! :)</p>
<p><a href="https://codepen.io/leaverou/pen/qBbQdGG">https://codepen.io/leaverou/pen/qBbQdGG</a></p>
<h2 id="update%3A-cjs-with-static-imports" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/import-non-esm-libraries-in-es-modules-with-client-side-vanilla-js/#update%3A-cjs-with-static-imports">Update: CJS with static imports</a></h2>
<p>After this article was posted, a clever hack was <a href="https://twitter.com/justinfagnani/status/1285325206811107329">pointed out to me on Twitter</a>:</p>
<p><a href="https://twitter.com/justinfagnani/status/1285325206811107329">https://twitter.com/justinfagnani/status/1285325206811107329</a></p>
<p>While this works great if you can have multiple separate files, it doesn’t work when you’re e.g. quickly trying out a pen. Data URIs to the rescue! <a href="https://codepen.io/leaverou/pen/XWXoEJq?editors=0010">Turns out you can <code>import</code> a module from a data URI</a>!</p>
<p>So let’s <a href="https://codepen.io/leaverou/pen/xxZmWvx">adapt our Rework example to use this</a>:</p>
<p><a href="https://codepen.io/leaverou/pen/xxZmWvx">https://codepen.io/leaverou/pen/xxZmWvx</a></p>
<h2 id="addendum%3A-esm-gripes" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/import-non-esm-libraries-in-es-modules-with-client-side-vanilla-js/#addendum%3A-esm-gripes">Addendum: ESM gripes</a></h2>
<p>Since I was bound to get questions about what my gripes are with ESM, I figured I should mention them pre-emptively.</p>
<p>First off, a little context. Nearly all of the JS I write is for libraries. <a href="https://github.com/leaverou">I write libraries as a hobby</a>, <a href="https://mavo.io/">I write libraries as my job</a>, and sometimes <a href="https://inspirejs.org/">I write libraries to help me do my job</a>. My job is usability (HCI) research (and specifically making programming easier), so I’m very sensitive to developer experience issues. I want my libraries to be usable not just by seasoned developers, but by novices too.</p>
<p>ESM has not been designed with novices in mind. It evolved from the CJS/UMD/AMD ecosystem, in which most voices are seasoned developers.</p>
<p>My main gripe with them, is how they expect full adoption, and settle for nothing less. There is no way to create a bundle of a library that can be used <em>both</em> traditionally, with a global, or as an ES module. There is also no standard way to import older libraries, or libraries using other module patterns (yes, this very post is about doing that, but essentially these are hacks, and there should be a better way). I understand the benefits of static analysis for imports and exports, but I wish there was a dynamic alternative to <code>export</code>, analogous to the dynamic <code>import()</code>.</p>
<p>In terms of migrating to ESM, I also dislike how opinionated they are: strict mode is great, but forcing it doesn’t help people trying to migrate older codebases. Restricting them to cross-origin is also a pain, using <code><script></code>s from other domains made it possible to quickly experiment with various libraries, and I would love for that to be true for modules too.</p>
<p>But overall, I’m excited that JS now natively supports a module mechanism, and I expect any library I release in the future to utilize it.</p>
Introspecting CSS via the CSS OM: Get supported properties, shorthands, longhands2020-07-27T00:00:00Zhttps://lea.verou.me/?p=3109<p>For <a href="https://github.com/LeaVerou/css-almanac/issues/10">some of the statistics</a> we are going to study for this year’s Web Almanac we may end up needing a list of CSS shorthands and their longhands. Now this is typically done by <a href="https://github.com/LeaVerou/prefixfree/blob/gh-pages/prefixfree.js#L298">maintaining a data structure by hand</a> or <a href="https://github.com/LeaVerou/prefixfree/blob/gh-pages/prefixfree.js#L298">guessing based on property name structure</a>. But I knew that if we were going to do it by hand, it’s very easy to miss a few of the less popular ones, and the naming rule where shorthands are a prefix of their longhands <a href="https://lists.w3.org/Archives/Public/www-style/2012Apr/0155.html">has failed to get standardized</a> and now has even more exceptions than it used to. And even if we do an incredibly thorough job, next year the data structure will be inaccurate, because CSS and its implementations evolve fast. The browser knows what the shorthands are, surely we should be able to get the information from it …right? Then we could use it directly if this is a client-side library, or in the case of the Almanac, where code needs to be fast because it will run on millions of websites, paste the precomputed result into whatever script we run.</p>
<p>There are essentially two steps for this:</p>
<ol>
<li>Get a list of all CSS properties</li>
<li>Figure out how to test if a given property is a shorthand and how to get its longhands if so.</li>
</ol>
<p>I decided to tell this story in the inverse order. In my exploration, I first focused on figuring out shorthands (2), because I had coded getting a list of properties many times before, but since (1) is useful in its own right (and probably in more use cases), I felt it makes more sense to examine that first.</p>
<p><em><strong>Note:</strong> I’m using <code>document.body</code> instead of a dummy element in these examples, because I like to experiment in <code>about:blank</code>, and it’s just there and because this way you can just copy stuff to the console and try it wherever, even right here while reading this post. However, if you use this as part of code that runs on a real website, it goes without saying that you should create and test things on a dummy element instead!</em></p>
<h2 id="getting-a-list-of-all-css-properties-from-the-browser" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/introspecting-css-via-the-css-om-getting-supported-properties-shorthands-longhands/#getting-a-list-of-all-css-properties-from-the-browser">Getting a list of all CSS properties from the browser</a></h2>
<p>In Chrome and Safari, this is as simple as <code>Object.getOwnPropertyNames(document.body.style)</code>. However, in Firefox, this doesn’t work. Why is that? To understand this (and how to work around it), we need to dig a bit deeper.</p>
<p>In Chrome and Safari, <code>element.style</code> is a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration"><code>CSSStyleDeclaration</code></a> instance. In Firefox however, it is a <a href="https://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties"><code>CSS2Properties</code></a> instance, which inherits from <code>CSSStyleDeclaration</code>. <a href="https://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties"><code>CSS2Properties</code></a> is an older interface, defined in <a href="https://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties">the DOM 2 Specification</a>, which is now obsolete. In the <a href="https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface">current relevant specification</a>, <code>CSS2Properties</code> is gone, and has been merged with <code>CSSStyleDeclaration</code>. However, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1290786">Firefox hasn’t caught up yet</a>.</p>
<p><img src="https://lea.verou.me/2020/07/introspecting-css-via-the-css-om-getting-supported-properties-shorthands-longhands/images/image-2.png" alt="" /></p>
<p>Firefox on the left, Safari on the right. Chrome behaves like Safari.</p>
<p>Since the properties are on <code>CSSStyleDeclaration</code>, they are not own properties of <code>element.style</code>, so <code>Object.getOwnPropertyNames()</code> fails to return them. However, we can extract the <code>CSSStyleDeclaration</code> instance by using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto"><code>__proto__</code></a> or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf">Object.getPrototypeOf()</a>, and then <code>Object.getOwnPropertyNames(Object.getPrototypeOf(document.body.style))</code> gives us what we want!</p>
<p>So we can combine the two to get a list of properties regardless of browser:</p>
<pre><code>let properties = Object.getOwnPropertyNames(
style.hasOwnProperty("background")?
style : style.__proto__
);
</code></pre>
<p>And then, we just drop non-properties, and de-camelCase:</p>
<pre><code>properties = properties.filter(p => style[p] === "") // drop functions etc
.map(prop => { // de-camelCase
prop = prop.replace(/[A-Z]/g, function($0) { return '-' + $0.toLowerCase() });
if (prop.indexOf("webkit-") > -1) {
prop = "-" + prop;
}
return prop;
});
</code></pre>
<p>You can see a <a href="https://codepen.io/leaverou/pen/eYJodjb?editors=0010">codepen with the result here</a>:</p>
<p><a href="https://codepen.io/leaverou/pen/eYJodjb?editors=0010">https://codepen.io/leaverou/pen/eYJodjb?editors=0010</a></p>
<h2 id="testing-if-a-property-is-a-shorthand-and-getting-a-list-of-longhands" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/introspecting-css-via-the-css-om-getting-supported-properties-shorthands-longhands/#testing-if-a-property-is-a-shorthand-and-getting-a-list-of-longhands">Testing if a property is a shorthand and getting a list of longhands</a></h2>
<p>The main things to note are:</p>
<ul>
<li>When you set a shorthand on an element’s inline style, you are essentially setting all its longhands.</li>
<li><code>element.style</code> is actually array-like, with numerical properties and <code>.length</code> that gives you the number of properties set on it. This means you can use the spread operator on it:</li>
</ul>
<pre><code>> document.body.style.background = "red";
> [...document.body.style]
< [
"background-image",
"background-position-x",
"background-position-y",
"background-size",
"background-repeat-x",
"background-repeat-y",
"background-attachment",
"background-origin",
"background-clip",
"background-color"
]
</code></pre>
<p>Interestingly, <code>document.body.style.cssText</code> serializes to <code>background: red</code> and not all the longhands.</p>
<p>There is one exception: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/all">The <code>all</code> property</a>. In Chrome, it does not quite behave as a shorthand:</p>
<pre><code>> document.body.style.all = "inherit";
> [...document.body.style]
< ["all"]
</code></pre>
<p>Whereas in Safari and Firefox, it actually returns every single property <em>that is not a shorthand</em>!</p>
<p><img src="https://lea.verou.me/2020/07/introspecting-css-via-the-css-om-getting-supported-properties-shorthands-longhands/images/image-1.png" alt="" /></p>
<p>Firefox and Safari expand <code>all</code> to literally all non-shorthand properties.</p>
<p>While this is interesting from a trivia point of view, it doesn’t actually matter for our use case, since we don’t typically care about <code>all</code> when constructing a list of shorthands, and if we do we can always add or remove it manually.</p>
<p>So, to recap, we can easily get the longhands of a given shorthand:</p>
<pre><code>function getLonghands(property) {
let style = document.body.style;
style[property] = "inherit"; // a value that works in every property
let ret = [...style];
style.cssText = ""; // clean up
return ret;
}
</code></pre>
<h2 id="putting-the-pieces-together" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/07/introspecting-css-via-the-css-om-getting-supported-properties-shorthands-longhands/#putting-the-pieces-together">Putting the pieces together</a></h2>
<p>You can see how all the pieces fit together (and the output!) in <a href="https://codepen.io/leaverou/pen/gOPEJxz?editors=0010">this codepen</a>:</p>
<p><a href="https://codepen.io/leaverou/pen/gOPEJxz?editors=0010">https://codepen.io/leaverou/pen/gOPEJxz?editors=0010</a></p>
<p>How many of these shorthands did you already know?</p>
Parsel: A tiny, permissive CSS selector parser2020-09-07T00:00:00Zhttps://lea.verou.me/?p=3126<p>I’ve <a href="https://lea.verou.me/tags/web-almanac/">posted before about my work for the Web Almanac</a> this year. To make it easier to calculate the stats about CSS selectors, we looked to use an existing selector parser, but most were too big and/or had dependencies or didn’t account for all selectors we wanted to parse, and we’d need to write our own walk and specificity methods anyway. So I did what I usually do in these cases: I wrote my own!</p>
<p>You can find it <a href="https://projects.verou.me/parsel/">here</a>: <a href="https://projects.verou.me/parsel/">https://projects.verou.me/parsel/</a></p>
<p><img src="https://lea.verou.me/2020/09/parsel-a-tiny-permissive-css-selector-parser/images/image.png" alt="" /></p>
<p>It not only parses CSS selectors, but also includes methods to walk the AST produced, as well as calculate specificity as an array and convert it to a number for easy comparison.</p>
<p>It is one of my first libraries released as an ES module, and there are instructions about both using it as a module, and as a global, for those who would rather not deal with ES modules yet, because convenient as ESM are, I wouldn’t want to exclude those less familiar with modern JS.</p>
<p>Please try it out and report any bugs! We plan to use it for Almanac stats in the next few days, so if you can spot bugs sooner rather than later, you can help that volunteer effort. I’m primarily interested in <strong>(realistic) valid selectors that are parsed incorrectly</strong>. I’m aware there are many invalid selectors that are parsed weirdly, but that’s not a focus (hence the “permissive” aspect, there are many invalid selectors it won’t throw on, and that’s by design to keep the code small, the logic simple, and the functionality future-proof).</p>
<h3 id="how-it-works" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/09/parsel-a-tiny-permissive-css-selector-parser/#how-it-works">How it works</a></h3>
<p>If you’re just interested in using this selector parser, read no further. This section is about how the parser works, for those interested in this kind of thing. :)</p>
<p>I <a href="https://github.com/LeaVerou/parsel/blob/master/parsel_aborted.js">first started by writing a typical parser, with character-by-character gobbling and different modes</a>, with code somewhat inspired by my familiarity with <a href="https://ericsmekens.github.io/jsep/">jsep</a>. I quickly realized that was a more fragile approach for what I wanted to do, and would result in a much larger module. I also missed the ease and flexibility of doing things with regexes.</p>
<p>However, since CSS selectors include strings and parens that can be nested, parsing them with regexes is a fool’s errand. <em>Nested structures are not <a href="https://en.wikipedia.org/wiki/Regular_language">regular languages</a></em> as my CS friends know. You cannot use a regex to find the closing parenthesis that corresponds to an opening parenthesis, since you can have other nested parens inside it. And it gets even more complex when there are other tokens that can nest, such as strings or comments. What if you have an opening paren that contains a string with a closing paren, like e.g. <code>("foo)")</code>? A regex would match the closing paren inside the string. In fact, parsing the language of nested parens (strings like <code>(()(()))</code>) with regexes is one of the typical (futile) exercises in a compilers course. Students struggle to do it because it’s an impossible task, and learn the hard way that not everything can be parsed with regexes.</p>
<p>Unlike a typical programming language with lots of nested structures however, the language of CSS selectors is more limited. There are only two nested structures: strings and parens, and they only appear in specific types of selectors (namely attribute selectors, pseudo-classes and pseudo-elements). Once we get those out of the way, everything else can be easily parsed by regexes. So I decided to go with a hybrid approach: The selector is first looked at character-by-character, to extract strings and parens. We only extract top-level parens, since anything inside them can be parsed separately (when it’s a selector), or not at all. The strings are replaced by a single character, as many times as the length of the string, so that any character offsets do not change, and the strings themselves are stored in a stack. Same with parens.</p>
<p>After that point, this modified selector language is a regular language that can be parsed with regexes. To do so, I follow an approach inspired by the early days of <a href="https://prismjs.com/">Prism</a>: An <a href="https://github.com/LeaVerou/parsel/blob/master/parsel.js#L1-L10">object literal of tokens</a> in the order they should be matched in, and <a href="https://github.com/LeaVerou/parsel/blob/master/parsel.js#L49">a function that tokenizes a string by iteratively matching tokens from an object literal</a>. In fact, this function was taken from an early version of Prism and modified.</p>
<p>After we have the list of tokens as a flat array, we can restore strings and parens, and then nest them appropriately to create an AST.</p>
<p>Also note that the token regexes use the new-ish <a href="https://2ality.com/2017/05/regexp-named-capture-groups.html">named capture groups</a> feature in ES2018, since it’s now <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Browser_compatibility">supported pretty widely</a> in terms of market share. For wider support, you can transpile :)</p>
Developer priorities throughout their career2020-09-16T00:00:00Zhttps://lea.verou.me/?p=3134<p>I made this chart in the amazing <a href="https://excalidraw.com/">Excalidraw</a> about two weeks ago:</p>
<p><img src="https://lea.verou.me/2020/09/developer-priorities-throughout-their-career/images/image-2.png" alt="" /></p>
<p>It only took me 10 minutes! Shortly after, my laptop broke down into repeated kernel panics, and it spent about 10 days in service (I was in a remote place when it broke, so it took some time to get it to service). Yesterday, I was finally reunited with it, turned it on, launched Chrome, and saw it again. It gave me a smile, and I realized I never got to post it, so I <a href="https://twitter.com/LeaVerou/status/1306001020636540934">tweeted this</a>:</p>
<p><a href="https://twitter.com/LeaVerou/status/1306001020636540934">https://twitter.com/LeaVerou/status/1306001020636540934</a></p>
<p>The tweet kinda blew up! It seems many, <em>many</em> developers identify with it. A few also disagreed with it, especially with the “Does it actually work?” line. So I figured I should write a bit about the rationale behind it. I originally <a href="https://twitter.com/LeaVerou/status/1306207138931445761">wrote it in a tweet</a>, but then I realized I should probably post it in a less transient medium, that is more well suited to longer text.</p>
<p>When somebody starts coding, getting the code to work is already difficult enough, so there is no space for other priorities. Learning to formalize one’s thought to the degree a computer demands, and then serialize this thinking with an unforgiving syntax, is <em>hard</em>. Writing code that works is THE priority, and whether it’s good code is not even a consideration.</p>
<p>For more experienced programmers, whether it works is ephemeral: today it works, tomorrow a commit causes a regression, the day after another commit fixes it (yes, even with TDD. No testsuite gets close to 100% coverage). Whereas readability & maintainability do not fluctuate much. If they are not prioritized from the beginning, they are much harder to accomplish when you already have a large codebase full of technical debt.</p>
<p>Code written by experienced programmers that doesn’t work, can often be fixed with hours or days of debugging. A nontrivial codebase that is not readable can take months or years to rewrite. So one tends to gravitate towards prioritizing what is easier to fix.</p>
<h3 id="the-%E2%80%9Cpeak-of-drought%E2%80%9D-and-other-over-abstractions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/09/developer-priorities-throughout-their-career/#the-%E2%80%9Cpeak-of-drought%E2%80%9D-and-other-over-abstractions">The “peak of drought” and other over-abstractions</a></h3>
<p>Many developers identified with the <em>“peak of drought”</em>. Indeed, like other aspects of maintainability, DRY is not even a concern at first. At some point, a programmer learns about the importance of DRY and gradually begins abstracting away duplication. However, you <em>can</em> have too much of a good thing: soon the need to abstract away any duplication becomes all consuming and leads to absurd, awkward abstractions which actually get in the way and produce needless couplings, often to avoid duplicating very little code, once. In my own <em>“peak of drought”</em> (which lasted far longer than the graph above suggests), I’ve written many useless functions, with parameters that make no sense, just to avoid duplicating a few lines of code once.</p>
<p><a href="https://dev.to/jeroendedauw/the-fallacy-of-dry">Many</a> <a href="https://dev.to/wuz/stop-trying-to-be-so-dry-instead-write-everything-twice-wet-5g33">articles</a> <a href="https://medium.com/better-programming/when-dry-doesnt-work-go-wet-6befda0444bf">have</a> <a href="http://joelabrahamsson.com/the-dry-obsession/">been</a> written about this phenomenon, so I’m not going to repeat their arguments here. As a programmer accumulates even more experience, they start seeing the downsides of over-abstraction and over-normalization and start favoring a more moderate approach which prioritizes readability over DRY when they are at odds.</p>
<p>A similar thing happens with <a href="https://en.wikipedia.org/wiki/Software_design_pattern">design patterns</a> too. At some point, a few years in, a developer reads a book or takes a course about design patterns. Soon thereafter, their code becomes so littered with design patterns that it is practically incomprehensible. <em>“When all you have is a hammer, everything looks like a nail”</em>. I have a feeling that Java and Java-like languages are particularly accommodating to this ailment, so this phenomenon tends to proliferate in their codebases. At some point, the developer has to go back to their past code, and they realize themselves that it is unreadable. Eventually, they learn to use design patterns when they are actually useful, and favor readability over design patterns when the two are at odds.</p>
<p>What aspects of your coding practice have changed over the years? How has your perspective shifted? What mistakes of the past did you eventually realize?</p>
The failed promise of Web Components2020-09-24T00:00:00Zhttps://lea.verou.me/?p=3147<p>Web Components had so much potential to empower HTML to do more, and make web development more accessible to non-programmers and easier for programmers. Remember how exciting it was every time we got new shiny HTML elements that actually <em>do stuff</em>? Remember how exciting it was to be able to do sliders, color pickers, dialogs, disclosure widgets straight in the HTML, without having to include any widget libraries?</p>
<p>The promise of Web Components was that we’d get this convenience, but for a much wider range of HTML elements, developed much faster, as nobody needs to wait for the full spec + implementation process. We’d just include a script, and boom, we have more elements at our disposal!</p>
<p>Or, that was the idea. Somewhere along the way, the space got flooded by JS frameworks aficionados, who revel in complex APIs, overengineered build processes and dependency graphs that look like the roots of a banyan tree.</p>
<p><img src="https://live.staticflickr.com/2025/32441377780_e3acf6de12_b.jpg" alt="" /></p>
<p>This is what the roots of a Banyan tree look like. <a href="https://www.flickr.com/photos/79721788@N00/32441377780/">Photo by David Stanley on Flickr (CC-BY)</a>.</p>
<p>Perusing the components on <a href="https://www.webcomponents.org/">webcomponents.org</a> fills me with anxiety, and I’m perfectly comfortable writing JS — I write JS for a living! What hope do those who can’t write JS have? Using a custom element from the directory often needs to be preceded by a ritual of npm flugelhorn, import clownshoes, build quux, all completely unapologetically because “here is my truckload of dependencies, yeah, what”. Many steps are even omitted, likely because they are “obvious”. Often, you wade through the maze only to find the component doesn’t work anymore, or is not fit for your purpose.</p>
<p>Besides setup, the main problem is that HTML is not treated with the appropriate respect in the design of these components. They are not designed as closely as possible to standard HTML elements, but <em>expect</em> JS to be written for them to do anything. HTML is simply treated as a shorthand, or worse, as merely a marker to indicate where the element goes in the DOM, with all parameters passed in via JS. I recall <a href="https://adactio.com/articles/12839#webcomponents">a wonderful talk by Jeremy Keith</a> a few years ago about this very phenomenon, where he discussed <a href="https://shop.polymer-project.org/">this e-shop Web components demo by Google</a>, which is the poster child of this practice. These are the entire contents of its <code><body></code> element:</p>
<pre><code class="language-html"><body>
<shop-app unresolved="">SHOP</shop-app>
<script src="node_assets/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
<script type="module" src="src/shop-app.js"></script>
<script>window.performance&&performance.mark&&performance.mark("index.html");</script>
</body>
</code></pre>
<p>If this is how Google is leading the way, how can we hope for contributors to design components that follow established HTML conventions?</p>
<p>Jeremy criticized this practice from the aspect of backwards compatibility: when JS is broken or not enabled, or the browser doesn’t support Web Components, the entire website is blank. While this is indeed a serious concern, my primary concern is one of <strong>usability</strong>: <strong>HTML is a lower barrier to entry language</strong>. Far more people can write HTML than JS. Even for those who do eventually write JS, it often comes after spending years writing HTML & CSS.</p>
<p>If components are designed in a way that requires JS, this excludes thousands of people from using them. And even for those who <em>can</em> write JS, HTML is often easier: you don’t see many people rolling their own sliders or using JS-based ones once <code><input type="range"></code> became widely supported, right?</p>
<p>Even when JS is unavoidable, it’s not black and white. A well designed HTML element can reduce the amount and complexity of JS needed to a minimum. Think of the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"><code><dialog></code></a> element: it usually does require *some* JS, but it’s usually rather simple JS. Similarly, the <code><video></code> element is perfectly usable just by writing HTML, and has a comprehensive JS API for anyone who wants to do fancy custom things.</p>
<p>The other day I was looking for a simple, dependency free, tabs component. You know, the canonical example of something that is easy to do with Web Components, the example 50% of tutorials mention. I didn’t even care what it looked like, it was for a testing interface. I just wanted something that is small and works like a normal HTML element. Yet, it proved so hard I ended up writing my own!</p>
<h3 id="can-we-fix-this%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/09/the-failed-promise-of-web-components/#can-we-fix-this%3F">Can we fix this?</a></h3>
<p>I’m not sure if this is a design issue, or a documentation issue. Perhaps for many of these web components, there are easier ways to use them. Perhaps there are vanilla web components out there that I just can’t find. Perhaps I’m looking in the wrong place and there is another directory somewhere with different goals and a different target audience.</p>
<p>But if not, and if I’m not alone in feeling this way, we need a directory of web components with strict inclusion criteria:</p>
<ul>
<li><strong>Plug and play.</strong> No dependencies, no setup beyond including one <code><script></code> tag. If a dependency is absolutely <em>needed</em> (e.g. in a map component it doesn’t make sense to draw your own maps), the component loads it automatically if it’s not already loaded.</li>
<li>Syntax and API follows <a href="https://www.smashingmagazine.com/2017/02/designing-html-apis/"><strong>conventions established by built-in HTML elements</strong></a> and anything that <em>can</em> be done without the component user writing JS, <em>is</em> doable without JS, per <a href="https://www.w3.org/2001/tag/doc/leastPower.html">the W3C principle of least power</a>.</li>
<li><strong>Accessible by default</strong> via sensible ARIA defaults, just like normal HTML elements.</li>
<li><strong>Themable</strong> via <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part"><code>::part()</code></a>, selective inheritance and custom properties. Very minimal style by default. Normal CSS properties should just “work” to the the extent possible.</li>
<li><strong>Only one component of a given type</strong> in the directory, that is <strong>flexible</strong> and <strong>extensible</strong> and continuously iterated on and improved by the community. Not 30 different sliders and 15 different tabs that users have to wade through. No branding, no silos of “component libraries”. Only elements that are designed as closely as possible to what a browser would implement in every way the current technology allows.</li>
</ul>
<p>I would be up for working on this if others feel the same way, since that is not a project for one person to tackle. <em>Who’s with me?</em></p>
<p><strong>UPDATE:</strong> Wow this post blew up! Thank you all for your interest in participating in a potential future effort. I’m currently talking to stakeholders of some of the existing efforts to see if there are any potential collaborations before I go off and create a new one. <a href="https://twitter.com/leaverou">Follow me on Twitter to hear about the outcome</a>!</p>
The --var: ; hack to toggle multiple values with one custom property2020-10-12T00:00:00Zhttps://lea.verou.me/?p=3162<p>What if I told you you could use a single property value to turn multiple different values on and off across multiple different properties and even across multiple CSS rules?</p>
<p>What if I told you you could turn this flat button into a glossy skeuomorphic button by just tweaking one custom property <code>--is-raised</code>, and that would set its border, background image, box and text shadows in one fell swoop?</p>
<p><img src="https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/images/image-1.png" alt="" /></p>
<iframe src="https://dabblet.com/gist/055d4c1d9590250d6633bdf63e37f9ca" style="border: 0; width: 100%; min-height: 500px"></iframe>
<p><em>How</em>, you may ask?</p>
<p>The crux of this technique is this: There are two custom property values that work almost everywhere there is a <code>var()</code> call with a fallback.</p>
<p>The more obvious one that you probably already know is the <code>initial</code> value, which makes the property just apply its fallback. So, in the following code:</p>
<pre><code>background: var(--foo, linear-gradient(white, transparent)) hsl(220 10% 50%);
border: 1px solid var(--foo, rgb(0 0 0 / .1));
color: rgb(0 0 0 var(--foo, / .8));
</code></pre>
<p>We can set <code>--foo</code> to <code>initial</code> to enable these “fallbacks” and append these values to the property value, adding a gradient, setting a border-color, and making the text color translucent in one go. But what to do when we want to turn these values <em>off</em>? Any non-initial value for <code>--foo</code> (that doesn’t create cycles) should work. But is there one that works in all three declarations?</p>
<p>It turns out there <em>is</em> another value that works everywhere, in every property a <code>var()</code> reference is present, and you’d likely never guess what it is (unless you have watched any of my CSS variable talks and have a good memory for passing mentions of things).</p>
<p><em>Intrigued?</em></p>
<p>It’s whitespace! <strong>Whitespace is significant in a custom property.</strong> When you write something like this:</p>
<pre><code>--foo: ;
</code></pre>
<p>This is <strong>not</strong> an invalid declaration. This is a declaration where the value of <code>--foo</code> is literally one space character. However, whitespace is valid in every CSS property value, everywhere a <code>var()</code> is allowed, and does not affect its computed value in any way. So, we can just set our property to one space (or even a comment) and not affect any other value present in the declaration. E.g. this:</p>
<pre><code>--foo: ;
background: var(--foo, linear-gradient(white, transparent)) hsl(220 10% 50%);
</code></pre>
<p>produces the same result as:</p>
<pre><code>background: hsl(220 10% 50%);
</code></pre>
<p>We can take advantage of this to essentially turn <code>var()</code> into a single-clause <code>if()</code> function and conditionally append values based on a single custom property.</p>
<p>As a proof of concept, here is the two button demo refactored using this approach:</p>
<iframe src="https://dabblet.com/gist/4524674b9b8c49d88808b10f1d9ce3ec" style="border: 0; width: 100%; min-height: 500px"></iframe>
<h3 id="limitations" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/#limitations">Limitations</a></h3>
<p>I originally envisioned this as a building block for a technique <em>horrible hack</em> to enable “mixins” in the browser, since <a href="https://www.xanthir.com/b4o00">@apply is now defunct</a>. However, the big limitation is that this only works for appending values to existing values — or setting a property to either a whole value or <code>initial</code>. There is no way to say “the background should be red if <code>--foo</code> is set and white otherwise”. Some such conditionals can be emulated with clever use of appending, but not most.</p>
<p>And of course there’s a certain readability issue: <code>--foo: ;</code> looks like a mistake and <code>--foo: initial</code> looks pretty weird, unless you’re aware of this technique. However, that can easily be solved with comments. Or even constants:</p>
<pre><code>:root {
--ON: initial;
--OFF: ;
}
button {
--is-raised: var(--OFF);
/* ... */
}
#foo {
--is-raised: var(--ON);
}
</code></pre>
<p>Also do note that eventually we will get a proper <code>if()</code> and won’t need such horrible hacks to emulate it, discussions are already underway [<a href="https://github.com/w3c/csswg-drafts/issues/5009">w3c/csswg-drafts#5009</a> <a href="https://github.com/w3c/csswg-drafts/issues/4731">w3c/csswg-drafts#4731</a>].</p>
<p>So what do you think? Horrible hack, useful technique, or both? 😀</p>
<h2 id="prior-art" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/#prior-art">Prior art</a></h2>
<p>Turns out this was independently discovered by two people before me:</p>
<ul>
<li>First, <a href="https://twitter.com/anatudor/status/1284160219963170816">the brilliant Ana Tudor circa 2017</a></li>
<li>Then <a href="https://github.com/propjockey/css-sweeper#css-is-a-programming-language-thanks-to-the-space-toggle-trick">James0x57 in April 2020</a></li>
</ul>
<p>And it was called “space toggle hack” in case you want to google it!</p>
Simple pie charts with fallback, today2020-11-12T00:00:00Zhttps://lea.verou.me/?p=3178<p>Five years ago, I had written <a href="https://www.smashingmagazine.com/2015/07/designing-simple-pie-charts-with-css/">this extensive Smashing Magazine article</a> detailing multiple different methods for creating simple pie charts, either with clever use of transforms and pseudo-elements, or with SVG <code>stroke-dasharray</code>. In the end, I mentioned creating pie charts with conic gradients, as a future technique. It was actually a writeup of <a href="https://www.youtube.com/results?search_query=verou+missing+slice&page&utm_source=opensearch">my “The Missing Slice” talk</a>, and an excerpt of my <a href="http://www.amazon.com/CSS-Secrets-Lea-Verou/dp/1449372635?tag=leaverou-20">CSS Secrets book</a>, which had just been published.</p>
<p>I was reminded of this article today by someone on Twitter:</p>
<p><a href="https://twitter.com/sam%5C_kent%5C_/status/1326805431390531584">https://twitter.com/sam\_kent\_/status/1326805431390531584</a></p>
<p>I suggested conic gradients, since they are <a href="https://caniuse.com/css-conic-gradients">now supported in >87% of users’ browsers</a>, but he needed to support IE11. He suggested using <a href="https://projects.verou.me/conic-gradient/">my polyfill</a> from back then, but this is not a very good idea today.</p>
<p><a href="https://twitter.com/fcorradini/status/1326880827825905665">https://twitter.com/fcorradini/status/1326880827825905665</a></p>
<p>Indeed, unless you <em>really</em> need to display conic gradients, even I would not recommend using the polyfill on a production facing site. It requires <a href="https://projects.verou.me/prefixfree/">-prefix-free</a>, which re-fetches (albeit from cache) your entire CSS and sticks it in a <code><style></code> element, with no sourcemaps since those were not a thing back when -prefix-free was written. If you’re already using -prefix-free, the polyfill is great, but if not, it’s way too heavy a dependency.</p>
<h3 id="pie-charts-with-fallback-(modern-browsers)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/#pie-charts-with-fallback-(modern-browsers)">Pie charts with fallback (modern browsers)</a></h3>
<p>Instead, what I would recommend is graceful degradation, i.e. to use the same color stops, but in a linear gradient.</p>
<p>We can use <code>@supports</code> and have quite an elaborate progress bar fallback. For example, take a look at this 40% pie chart:</p>
<pre><code>.pie {
height: 20px;
background: linear-gradient(to right, deeppink 40%, transparent 0);
background-color: gold;
}
@supports (background: conic-gradient(white, black)) {
.pie {
width: 200px; height: 200px;
background-image: conic-gradient(deeppink 40%, transparent 0);
border-radius: 50%;
}
}
</code></pre>
<iframe src="https://dabblet.com/gist/9a05fd9dad59b6c0edfb99d49a7a4426" style="border: 0; width: 100%; height: 500px;"></iframe>
<p>This is what it looks like in Firefox 82 (conic gradients are scheduled to ship unflagged in Firefox 83) or IE11:</p>
<p><img src="https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/images/image.png" alt="" /></p>
<p>Note that because <code>@supports</code> is only used for the pie and not the fallback, the lack of IE11 support for it doesn’t affect us one iota.</p>
<p>If relatively modern browsers are all we care about, we could even use CSS variables for the percentage and the color stops, to avoid duplication, and to be able to set the percentage from the markup:</p>
<pre><code class="language-html"><div class="pie" style="--p: 40%"></div>
</code></pre>
<pre><code>.pie {
height: 20px;
--stops: deeppink var(--p, 0%), transparent 0;
background: linear-gradient(to right, var(--stops));
background-color: gold;
}
@supports (background: conic-gradient(white, black)) {
.pie {
width: 200px; height: 200px;
background-image: conic-gradient(var(--stops));
border-radius: 50%;
}
}
</code></pre>
<p>You can use a similar approach for 3 or more segments, or for a vertical bar.</p>
<p>One issue with this approach is that our layout needs to work well with two charts of completely different proportions. To avoid that, we could just use a square:</p>
<pre><code>.pie {
width: 200px;
height: 200px;
background: linear-gradient(to right, deeppink 40%, transparent 0) gold;
}
@supports (background: conic-gradient(white, black)) {
.pie {
background-image: conic-gradient(deeppink 40%, transparent 0);
border-radius: 50%;
}
}
</code></pre>
<p>which produces this in IE11:</p>
<p><img src="https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/images/image-8.png" alt="" /></p>
<p>Granted, a square progress bar is not the same, but it can still convey the same relationship and is easier to design a layout around it since it always has the same aspect ratio.</p>
<h3 id="why-not-use-radial-gradients%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/#why-not-use-radial-gradients%3F">Why not use radial gradients?</a></h3>
<p>You might be wondering, why not just use a radial gradient, which could use the same dimensions and rounding. Something like this:</p>
<p><img src="https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/images/image-4.png" alt="" /></p>
<p>There are two problems with this. The first one may be obvious: Horizontal or vertical bars are common for showing the proportional difference between two amounts, albeit less good than a pie chart because it’s harder to compare with 50% at a glance (<em>yes Tufte, pie charts can be better for some things!</em>). Such circular graphs are very uncommon. And for good reason: Drawn naively (e.g. in our case if the radius of the pink circle is 40% of the radius of the yellow circle), their areas do not have the relative relationship we want to depict.</p>
<p>Why is that? Let <em>r</em> be the radius of the yellow circle. As we know from middle school, the area of the entire circle is π_r_², so the area of the yellow ring is π_r_² - (area of pink circle). The area of the pink circle is π(0.4_r_)² = 0.16π_r_². Therefore, the area of the yellow ring is π_r_² - 0.16π_r_² = 0.84π_r_² and their relative ratio is 0.16π_r_² / 0.84π_r_² = 0.16 / 0.84 ≅ 0.19 which is a far cry from the 40/60 (≅ 0.67) we were looking for!</p>
<p>Instead, if we wanted to draw a similar visualization to depict the correct relationship, we need to start from the ratio and work our way backwards. Let <em>r</em> be the radius of the yellow circle and <em>kr</em> the radius of the pink circle. Their ratio is π(<em>kr</em>)² / (π_r_² - π(<em>kr</em>)²) = 4/6 ⇒
_k_² / (1 - _k_²) = 4/6 ⇒
(1 - _k_²) / _k_² = 6/4 ⇒
1/_k_² - 1 = 6/4 ⇒
1/_k_² = 10/4 ⇒
k = 2 / sqrt(10) ≅ .632
Therefore, the radius of the pink circle should be around 63.2% of the radius of the yellow circle, and a more correct chart would look like this:</p>
<p><img src="https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/images/image-7.png" alt="" /></p>
<p>In the general case where the pink circle is depicting the percentage <em>p</em>, we’d want the radius of the pink circle to be sqrt(1 / <em>p</em>) the size of the yellow circle. That’s a fair bit of calculations that we can’t yet automate (though <a href="https://www.w3.org/TR/css-values/#exponent-funcs"><code>sqrt()</code> is coming</a>!). Moral of the story: use a bar as your fallback!</p>
The case for Weak Dependencies in JS2020-11-19T00:00:00Zhttps://lea.verou.me/?p=3199<p>Earlier today, I was briefly entertaining the idea of writing a library to wrap and enhance querySelectorAll in certain ways. I thought I’d rather not introduce a <a href="http://projects.verou.me/parsel/">Parsel</a> dependency out of the box, but only use it to parse selectors properly when it’s available, and use more crude regex when it’s not (which would cover most use cases for what I wanted to do).</p>
<p>In the olden days, where every library introduced a global, I could just do:</p>
<pre><code class="language-js">if (window.Parsel) {
let ast = Parsel.parse();
// rewrite selector properly, with AST
}
else {
// crude regex replace
}
</code></pre>
<p>However, with ESM, there doesn’t seem to be a way to detect whether a module is imported, without actually importing it yourself.</p>
<p>I <a href="https://twitter.com/LeaVerou/status/1329389035249422336">tweeted</a> about this…</p>
<p><a href="https://twitter.com/LeaVerou/status/1329389035249422336">https://twitter.com/LeaVerou/status/1329389035249422336</a></p>
<p>I thought this was a common paradigm, and everyone would understand why this was useful. However, I was surprised to find that most people were baffled about my use case. Most of them thought I was either talking about conditional imports, or error recovery after failed imports.</p>
<p>I suspect it might be because my primary perspective for writing JS is that of a library author, where I do not control the host environment, whereas for most developers, their primary perspective is that of writing JS for a specific app or website.</p>
<p>After <a href="https://twitter.com/LeaVerou/status/1329389035249422336">Kyle Simpson asked me to elaborate</a> about the use case, I figured a blog post was in order.</p>
<p>The use case is essentially <strong>progressive enhancement</strong> (in fact, I toyed with the idea of titling this blog post <em><strong>“Progressively Enhanced JS”</strong></em>). If library X is loaded already by other code, do a more elaborate thing and cover all the edge cases, otherwise do a more basic thing. It’s for dependencies that are not really <strong>depend</strong>encies, but more like <strong>nice-to-haves</strong>.
We often see modules that do things really well, but use a ton of dependencies and add a lot of weight, even to the simplest of projects, because they need to cater to all the edge cases that we may not care about. We also see modules that are dependency free, but that’s because lots of things are implemented more crudely, or certain features are not there.</p>
<p>This paradigm gives you the best of both worlds: <strong>Dependency free</strong> (or low dependency) modules, that can use what’s available to improve how they do things with <strong>zero additional impact</strong>.</p>
<p>Using this paradigm, the <strong>size of these dependencies is not a concern</strong>, because they are <strong>optional peer dependencies</strong>, so one can pick the best library for the job without being affected by bundle size. Or even use multiple! One does not even need to pick one dependency for each thing, they can support bigger, more complete libraries when they’re available and fall back to micro-libraries when they are not.</p>
<p>Some examples besides the one in the first paragraph:</p>
<ul>
<li>A Markdown to HTML converter that also syntax highlights blocks of code if <a href="https://prismjs.com/">Prism</a> is present. Or it could even support multiple different highlighters!</li>
<li>A code editor that uses <a href="https://projects.verou.me/incrementable">Incrementable</a> to make numbers incrementable via arrow keys, if it’s present</li>
<li>A templating library that also uses <a href="https://bevacqua.github.io/dragula/">Dragula</a> to make items rearrangable via drag & drop, if present</li>
<li>A testing framework that uses <a href="https://atomiks.github.io/tippyjs/">Tippy</a> for nice informational popups, when it’s available</li>
<li>A code editor that shows code size (in KB) if a library to measure that is included. Same editor can also show gzipped code size if a gzip library is included.</li>
<li>A UI library that uses a custom element if it’s available or the closest native one when it’s not (e.g. a fancy date picker vs <code><input type="date"></code> ) when it isn’t. Or <a href="https://projects.verou.me/awesomplete/">Awesomplete</a> for autocomplete when it’s available, and fall back to a simple <code><datalist></code> when it isn’t.</li>
<li>Code that uses a date formatting library when one is already loaded, and falls back to <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat"><code>Intl.DateTimeFormat</code></a> when it’s not.</li>
</ul>
<p>This pattern can even be <strong>combined with conditional loading</strong>: e.g. we check for all known syntax highlighters and load Prism if none are present.</p>
<p>To recap, some of the <strong>main benefits</strong> are:</p>
<ul>
<li><strong>Performance:</strong> If you’re loading modules over the network HTTP requests are expensive. If you’re pre-bundling it increases bundle size. Even if code size is not a concern, runtime performance is affected if you take the slow but always correct path when you don’t need it and a more crude approach would satisfice.</li>
<li><strong>Choice:</strong> Instead of picking one library for the thing you need, you can support multiple. E.g. multiple syntax highlighters, multiple Markdown parsers etc. If a library is always needed to do the thing you want, you can load it conditionally, if none of the ones you support are loaded already.</li>
</ul>
<h2 id="are-weak-dependencies-an-antipattern%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/11/the-case-for-weak-dependencies-in-js/#are-weak-dependencies-an-antipattern%3F">Are weak dependencies an antipattern?</a></h2>
<p>Since this article was posted, some of the feedback I got was along the lines of <em>“Weak dependencies are an antipattern because they are unpredictable. What if you have included a library but don’t want another library to use it? You should instead use parameters to explicitly provide references to these libraries.”</em></p>
<p>There are several counterpoints to make here.</p>
<p>First, if weak dependencies are used well, they are only used to <em>enhance</em> the default/basic behavior, so it’s highly unlikely that you’d want to turn that off and fall back to the default behavior.</p>
<p>Second, weak dependencies and parameter injection are not mutually exclusive. They can work together and complement each other, so that the weak dependencies provide sensible defaults that the parameters can then tweak further (or disable altogether). <em>Only</em> having parameter injection imposes a high upfront cognitive cost for using the library (see <em><a href="https://en.wikipedia.org/wiki/Convention_over_configuration">Convention over Configuration</a></em>). <strong>Good APIs make simple things easy and complex things possible.</strong> The common case is that if you’ve loaded e.g. a syntax highlighter, you’d want to use it to syntax highlight, and if you’ve loaded a parser, you’d prefer it over parsing with regexes. The obscure edge cases where you wouldn’t want to highlight or you want to provide a different parser can still be possible via parameters, but should not be the only way.</p>
<p>Third, the end user-developer may not even be aware of all the libraries that are being loaded, so they may already have a library loaded for a certain task but not know about it. The weak dependencies pattern operates directly on which modules are loaded so it doesn’t suffer from this problem.</p>
<h2 id="how-could-this-work-with-esm%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/11/the-case-for-weak-dependencies-in-js/#how-could-this-work-with-esm%3F">How could this work with ESM?</a></h2>
<p>Some people (mostly fellow library authors) *did* understand what I was talking about, and expressed some ideas about how this would work.</p>
<p><strong>Idea 1:</strong> A global module loaded cache could be a low-level way to implement this, and something CJS supports out of the box apparently.</p>
<p><a href="https://twitter.com/WebReflection/status/1329396560694796290">https://twitter.com/WebReflection/status/1329396560694796290</a></p>
<p><strong>Idea 2:</strong> A global registry where modules can register themselves on, either with an identifier, or a SHA hash
<strong>Idea 3:</strong> An <code>import.whenDefined(moduleURL)</code> promise, though that makes it difficult to deal with the module not being present at all, which is the whole point.</p>
<p><a href="https://twitter.com/WebReflection/status/1329420308491677696">https://twitter.com/WebReflection/status/1329420308491677696</a></p>
<p><a href="https://twitter.com/jcampbell%5C_05/status/1329413956474187777">https://twitter.com/jcampbell\_05/status/1329413956474187777</a></p>
<p><strong>Idea 4:</strong> Monitoring <code><link rel="modulepreload"></code>. The problem is that not all modules are loaded this way.</p>
<p><a href="https://twitter.com/getify/status/1329407281797222401">https://twitter.com/getify/status/1329407281797222401</a></p>
<p><strong>Idea 5:</strong> I was thinking of a function like <code>import()</code> that resolves with the module (same as a regular dynamic import) only when the module is already loaded, or rejects when it’s not (which can be caught). In fact, it could even use the same functional notation, with a second argument, like so:</p>
<pre><code class="language-js">import("https://cool-library", {weak: true});
</code></pre>
<p>Nearly all of these proposals suffer from one of the following problems.</p>
<p>Those that are <strong>URL based</strong> mean that only modules loaded from the same URL would be recognized. The same library loaded over a CDN vs locally would not be recognized as the same library.</p>
<p>One way around this is to expose a list of URLs, like the first idea, and allow to listen for changes to it. Then these URLs can be inspected and those which <em><strong>might</strong></em> belong to the module we are looking for can be further inspected by dynamically importing and inspecting their exports (importing already imported modules is a pretty cheap operation, the browser does de-duplicate the request).</p>
<p>Those that are <strong>identifier based</strong>, depend on the module to register itself with an identifier, so only modules that want to be exposed, will be. This is the closest to the old global situation, but would suffer in the transitional period until most modules use it. And of course, there is the potential for clashes. Though the API could take care of that, by essentially using a hashtable and adding all modules that register themselves with the same identifier under the same “bucket”. Code reading the registry would then be responsible for filtering.</p>
Position Statement for the 2020 W3C TAG Election2020-11-30T00:00:00Zhttps://lea.verou.me/?p=3216<p><strong>Update:</strong> <a href="https://www.w3.org/blog/news/archives/8846">I got elected!!</a> Thank you so much to every W3C member organization who voted for me. 🙏🏼 Now on to making the Web better, alongside fellow TAG members!</p>
<p><em><strong>Context:</strong> I’m running for one of the four open seats in this year’s W3C TAG <a href="https://www.w3.org/2020/12/07-tag-nominations">election</a>. The W3C Technical Architecture Group (TAG) is the Working Group that ensures that Web Platform technologies are usable and follow consistent design principles, whether they are created inside or outside W3C. It advocates for the needs of everyone who uses the Web and everyone who works on the Web. If you work for a company that is a <a href="https://www.w3.org/Consortium/Member/List">W3C Member</a>, please consider encouraging your AC rep to vote for me! My candidate statement follows.</em></p>
<p>Hi, I’m <a href="https://lea.verou.me/">Lea Verou</a>. Equally at home in Web development, the standards process, and programming language design, I bring a rarely-found cross-disciplinary understanding of the full stack of front-end development.</p>
<p>I have a thorough and fundamental understanding of all the core technologies of the Web Platform: HTML, CSS, JS, DOM, and SVG. I bring the experience and perspective of having worked as a web designer & developer in the trenches — not in large corporate systems, but on smaller, independent projects for clients, the type of projects that form the majority of the Web. I have started <a href="https://github.com/leaverou/">many</a> open source projects, used on millions of websites, large and small. <a href="https://hacks.mozilla.org/2014/07/event-listeners-popup-media-sidebar-cubic-bezier-editor-more-firefox-developer-tools-episode-33/#content-main:~:text=This%20feature%20used%20open%20source%20code%20from%20Lea%20Verou%E2%80%99s%20cubic%2Dbezier.com">Some</a> of my work has been incorporated in browser dev tools, and some has helped push CSS implementations forwards.</p>
<p>However, unlike most web developers, I am experienced in working within W3C, both as a longtime <a href="https://www.w3.org/groups/wg/css/participants">member of the CSS Working Group</a>, as well as a W3C Staff alumnus. This experience has given me a fuller grasp of Web technology development: not just the authoring side, but also the needs and constraints of implementation teams, the kinds of problems that tend to show up in our work, and the design principles we apply. I understand in practice how the standards process at W3C addresses the problems and weighs up the necessary compromises — from high-level design changes to minute details — to create successful standards for the Web.</p>
<p>I have spent over six years doing PhD research at MIT on the intersection of programming language design and human-computer interaction. <a href="https://lea.verou.me/publications/#research">My research</a> has been published in top-tier peer-reviewed academic venues. My strong usability background gives me the ability to identify API design pitfalls early on in the design process.</p>
<p>In addition, I have been teaching web technologies for over a decade, both to professional web developers, through my <a href="https://lea.verou.me/speaking">numerous talks, workshops</a>, and bestselling <a href="https://lea.verou.me/publications/#books">book</a>, and as an <a href="http://web.mit.edu/6.813/www/sp18/">instructor</a> and <a href="https://designftw.mit.edu/">course co-creator for MIT</a>. This experience helps me to easily identify aspects of API design that can make a technology difficult to learn and conceptualize.</p>
<p>If elected, I will work with the rest of the TAG to:</p>
<ul>
<li>Ensure that web technologies are not only powerful, but also learnable and approachable, with a smooth ease-of-use to complexity curve.</li>
<li>Ensure that where possible, commonly needed functionality is available through approachable declarative HTML or CSS syntax and not solely through JS APIs.</li>
<li>Work towards making the Web platform more extensible, to allow experienced developers to encapsulate complexity and make it available to novice authors, empowering the latter to create compelling content. Steps have been made in this direction with Web Components and the Houdini specifications, but there are still many gaps that need to be addressed.</li>
<li>Record design principles that are often implicit knowledge in standards groups, passed on but never recorded. Explicit design principles help us keep technologies internally consistent, but also assist library developers who want to design APIs that are consistent with the Web Platform and feel like a natural extension of it. A great start has been made with the initial drafts of the <a href="https://w3ctag.github.io/design-principles/">Design Principles document</a>, but there is still a lot to be done.</li>
<li>Guide those seeking TAG review, some of whom may be new to the standards process, to improve their specifications.</li>
</ul>
<p>Having worn all these hats, I can understand and empathize with the needs of designers and developers, authors and implementers, practitioners and academics, putting me in a unique position to help ensure the Web Platform remains consistent, usable, and inclusive.</p>
<p><em>I would like to thank <a href="https://openjsf.org/">Open JS Foundation</a> and <a href="https://bocoup.com/">Bocoup</a> for graciously funding my TAG-related travel, in the event that I am elected.</em></p>
<h2 id="selected-endorsements" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/11/tag/#selected-endorsements">Selected endorsements</a></h2>
<p><a href="https://en.wikipedia.org/wiki/Tantek_%C3%87elik">Tantek Çelik</a>, Mozilla’s AC representative, longtime CSS WG member, and <a href="https://en.wikipedia.org/wiki/Tantek_%C3%87elik">creator of many popular technologies</a>:</p>
<blockquote>
<p>I have had the privilege of working with Lea in the CSS Working Group, and in the broader web development community for many years. Lea is an expert in the practical real-world-web technologies of the W3C, how they fit together, has put them into practice, has helped contribute to their evolution, directly in specs and in working groups. She’s also a passionate user & developer advocate, both of which I think are excellent for the TAG.</p>
<p><a href="https://lists.w3.org/Archives/Member/w3c-ac-forum/2021JanMar/0015.html">Source</a>: <a href="https://lists.w3.org/Archives/Member/w3c-ac-forum/2021JanMar/0015.html">https://lists.w3.org/Archives/Member/w3c-ac-forum/2021JanMar/0015.html</a></p>
</blockquote>
<p><a href="https://florian.rivoal.net/">Florian Rivoal</a>, CSS WG Invited Expert and editor of <a href="https://florian.rivoal.net/cv.html#publications">several specifications</a>, elected <a href="https://www.w3.org/2002/ab/">W3C AB member</a>, ex-Opera:</p>
<p><a href="https://twitter.com/frivoal/status/1336857605063417856">https://twitter.com/frivoal/status/1336857605063417856</a></p>
<p><a href="http://fantasai.inkedblade.net/">Elika Etemad aka fantasai</a>, prolific editor of dozens of W3C specifications, CSS WG member for over 16 years, and elected <a href="https://www.w3.org/2002/ab/">W3C AB member</a>:</p>
<blockquote>
<p>One TPAC long ago, several members of the TAG on a recruiting spree went around asking people to run for the TAG. I personally turned them down for multiple reasons (including that I’m only a very poor substitute for David Baron), but it occurred to me recently that there was a candidate that they do
need: Lea Verou.</p>
<p>Lea is one of those elite developers whose technical expertise ranges across the entire Web platform. She doesn’t just use HTML, CSS, JS, and SVG, she pushes the boundaries of what they’re capable of. Meanwhile her authoring experience spans JS libraries to small site design to CSS+HTML print publications, giving her a personal appreciation of a wide variety of use cases.
Unlike most other developers in her class, however, Lea also brings her experience working within W3C as a longtime member of the CSS Working Group.</p>
<p>I’ve seen firsthand that she is capable of participating at the deep and excruciatingly detailed level that we operate here, and that her attention is not just on the feature at hand but also the system and its usability and coherence as a whole. She knows how the standards process works, how use cases and implementation constraints drive our design decisions, and how participation in the arcane discussions at W3C can make a real difference in the future usability of the Web.</p>
<p>I’m recommending her for the TAG because she’s able to bring a perspective that is needed and frequently missing from our technical discussions which so often revolve around implementers, and because elevating her to TAG would give her both the opportunity and the empowerment to bring that perspective to more of our Web technology development here at W3C and beyond.</p>
<p><a href="https://lists.w3.org/Archives/Member/w3c-ac-forum/2020OctDec/0055.html">Source</a>: <a href="https://lists.w3.org/Archives/Member/w3c-ac-forum/2020OctDec/0055.html">https://lists.w3.org/Archives/Member/w3c-ac-forum/2020OctDec/0055.html</a></p>
</blockquote>
<p><a href="https://www.brucelawson.co.uk/">Bruce Lawson</a>, Opera alumni, world renowned accessibility expert, speaker, author:</p>
<p><a href="https://twitter.com/brucel/status/1336260046691438594">https://twitter.com/brucel/status/1336260046691438594</a></p>
<p>Brian Kardell, AC representative for both Open JS Foundation and Igalia:</p>
<blockquote>
<p>The OpenJS Foundation is very pleased to nominate and offer our support for Lea Verou to the W3C TAG. We believe that she brings a fresh perspective, diverse background and several kinds of insight that would be exceptionally useful in the TAG’s work.</p>
<p><a href="https://www.w3.org/2020/12/07-tag-nominations#lv">Source</a>: <a href="https://www.w3.org/2020/12/07-tag-nominations#lv">https://www.w3.org/2020/12/07-tag-nominations#lv</a></p>
</blockquote>
<blockquote>
<p>Lea Verou is another easy choice for me. Lea brings a really diverse background, set of perspectives and skills to the table. She’s worked for the W3C, she’s a great communicator to developers (this is definitely a great skill in TAG whose outreach is important), she’s worked with small teams, produced a number of popular libraries and helped drive some interesting standards. The OpenJS Foundation was pleased to nominate her, but Frontiers and several others were also supportive. Lea also deserves “high marks”.</p>
<p><a href="https://bkardell.com/blog/TAG-2021.html">Source</a>: <a href="https://bkardell.com/blog/TAG-2021.html">https://bkardell.com/blog/TAG-2021.html</a></p>
</blockquote>
Writable getters2020-12-23T00:00:00Zhttps://lea.verou.me/?p=3246<figure>
<p><img src="https://lea.verou.me/2020/12/writable-getters/images/ouroboros.jpg" alt="" /></p>
<figcaption>
<p>Setters removing themselves are reminiscent of <a href="https://en.wikipedia.org/wiki/Ouroboros">Ouroboros</a>, the serpent eating its own tail, an ancient symbol. <a href="https://commons.wikimedia.org/wiki/File:The_serpent_Ouroboros,_from_Cyprianus,_18th_C_Wellcome_L0036620.jpg">Media credit</a></p>
</figcaption>
</figure>
<p>A pattern that has come up a few times in my code is the following: an object has a property which defaults to an expression based on its other properties <em>unless</em> it’s explicitly set, in which case it functions like a normal property. Essentially, the expression functions as a default value.</p>
<p>Some examples of use cases:</p>
<ul>
<li>An object where a default <code>id</code> is generated from its <code>name</code> or <code>title</code>, but can also have custom ids.</li>
<li>An object with information about a human, where <code>name</code> can be either specified explicitly or generated from <code>firstName</code> and <code>lastName</code> if not specified.</li>
<li>An object with parameters for drawing an ellipse, where <code>ry</code> defaults to <code>rx</code> if not explicitly set.</li>
<li>An object literal with date information, and a <code>readable</code> property which formats the date, but can be overwritten with a custom human-readable format.</li>
<li>An object representing parts of a Github URL (e.g. username, repo, branch) with an <code>apiCall</code> property which can be either customized or generated from the parts <em>(this is actually the <a href="https://github.com/mavoweb/mavo/pull/670#issuecomment-749585736">example</a> which prompted this blog post)</em></li>
</ul>
<p>Ok, so now that I convinced you about the utility of this pattern, how do we implement it in JS?</p>
<p>Our first attempt may look something like this:</p>
<pre><code>let lea = {
name: "Lea Verou",
get id() {
return this.name.toLowerCase().replace(/\W+/g, "-");
}
}
</code></pre>
<p><em><strong>Note:</strong> We are going to use object literals in this post for simplicity, but the same logic applies to variations using <code>Object.create()</code>, or a class <code>Person</code> of which <code>lea</code> is an instance.</em></p>
<p>Our first attempt doesn’t quite work as you might expect:</p>
<pre><code>lea.id; // "lea-verou"
lea.id = "lv";
lea.id; // Still "lea-verou"!
</code></pre>
<p>Why does this happen? The reason is that the presence of the getter turns the property into an <em>accessor</em>, and thus, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Description:~:text=Property%20descriptors%20present%20in%20objects%20come,two%20flavors%3B%20it%20cannot%20be%20both.">it cannot also hold data</a>. If it doesn’t have a setter, then simply nothing happens when it is set.</p>
<p>However, we can have a setter that, when invoked, <em>deletes the accessor and replaces it with a data property</em>:</p>
<pre><code>let lea = {
name: "Lea Verou",
get id() {
return this.name.toLowerCase().replace(/\W+/g, "-");
},
set id(v) {
delete this.id;
return this.id = v;
}
}
</code></pre>
<h2 id="abstracting-the-pattern-into-a-helper" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/12/writable-getters/#abstracting-the-pattern-into-a-helper">Abstracting the pattern into a helper</a></h2>
<p>If we find ourselves needing this pattern in more than one places in our codebase, we could abstract it into a helper:</p>
<pre><code>function writableGetter(o, property, getter, options = {}) {
Object.defineProperty(o, property, {
get: getter,
set (v) {
delete this[property];
return this[property] = v;
},
enumerable: true,
configurable: true,
...options
});
}
</code></pre>
<p>Note that we used <code>Object.defineProperty()</code> here instead of the succinct <code>get</code>/<code>set</code> syntax. Not only is the former more convenient for augmenting pre-existing objects, but also it allows us to customize enumerability, while the latter just defaults to <code>enumerable: true</code>.</p>
<p>We’d use the helper like this:</p>
<pre><code>let lea = {name: "Lea Verou"};
writableGetter(lea, "id", function() {
return this.name.toLowerCase().replace(/\W+/g, "-");
}, {enumerable: false});
</code></pre>
<h2 id="overwriting-the-getter-with-a-different-getter" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/12/writable-getters/#overwriting-the-getter-with-a-different-getter">Overwriting the getter with a <em>different</em> getter</a></h2>
<p>This works when we want to overwrite with a static value, but what if we want to overwrite with a <em>different</em> getter? For example, consider the date use case: what if we want to maintain a single source of truth for the date components and only overwrite the format, as a function, so that when the date components change, the formatted date updates accordingly?</p>
<p>If we are confident that setting the property to an actual function value wouldn’t make sense, we could handle that case specially, and create a new getter instead of a data property:</p>
<pre><code>function writableGetter(o, property, getter, options = {}) {
return Object.defineProperty(o, property, {
get () {
return getter.call(this);
},
set (v) {
if (typeof v === "function") {
getter = v;
}
else {
delete this[property];
return this[property] = v;
}
},
enumerable: true,
configurable: true,
...options
});
}
</code></pre>
<p>Do note that if we set the property to a static value, and try to set it to a function after that, it will just be a data property that creates a function, since we’ve deleted the accessor that handled functions specially. If that is a significant concern, we can maintain the accessor and just update the getter:</p>
<pre><code>function writableGetter(o, property, getter, options = {}) {
return Object.defineProperty(o, property, {
get () {
return getter.call(this);
},
set (v) {
if (typeof v === "function") {
getter = v;
}
else {
getter = () => v;
}
},
enumerable: true,
configurable: true,
...options
});
}
</code></pre>
<h2 id="improving-the-dx-of-our-helper" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/12/writable-getters/#improving-the-dx-of-our-helper">Improving the DX of our helper</a></h2>
<p>While this was the most straightforward way to define a helper, it doesn’t <em>feel</em> very natural to use. Our object definition is now scattered in multiple places, and readability is poor. This is often the case when we start implementing before designing a UI. In this case, writing the helper is the implementation, and its calling code is effectively the UI.</p>
<p><strong>It’s always a good practice to start designing functions by writing a call to that function</strong>, as if a tireless elf working for us had already written the implementation of our dreams.</p>
<p>So how would we <em>prefer</em> to write our object? I’d actually prefer to use the more readable <code>get()</code> syntax, and have everything in one place, then somehow <em>convert</em> that getter to a writable getter. Something like this:</p>
<pre><code>let lea = {
name: "Lea Verou",
get id() {
return this.name.toLowerCase().replace(/\W+/g, "-");
}
}
makeGetterWritable(lea, "id", {enumerable: true});
</code></pre>
<p>Can we implement something like this? Of course. This is JS, we can do anything!</p>
<p>The main idea is that we read back the descriptor our <code>get</code> syntax created, fiddle with it, then stuff it back in as a new property:</p>
<pre><code>function makeGetterWritable(o, property, options) {
let d = Object.getOwnPropertyDescriptor(o, property);
let getter = d.get;
d.get = function() {
return getter.call(this);
};
d.set = function(v) {
if (typeof v === "function") {
getter = v;
}
else {
delete this[property];
return this[property] = v;
}
};
// Apply any overrides, e.g. enumerable
Object.assign(d, options);
// Redefine the property with the new descriptor
Object.defineProperty(o, property, d)
}
</code></pre>
<h2 id="other-mixed-data-accessor-properties" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2020/12/writable-getters/#other-mixed-data-accessor-properties">Other mixed data-accessor properties</a></h2>
<p>While JS is very firm in its distinction of accessor properties and data properties, the reality is that we often need to combine the two in different ways, and conceptually it’s more of a <em>data-accessor spectrum</em> than two distinct categories. Here are a few more examples where the boundary between data property and accessor property is somewhat …murky:</p>
<ul>
<li><em>“Live” data properties</em>: properties which execute code to produce side effects when they are get or set, but still hold data like a regular data property. This can be faked by having a helper that creates a hidden data property. This idea is the core of <a href="https://blissfuljs.com/docs#fn-live"><code>Bliss.live()</code></a>.</li>
<li><em>Lazy evaluation</em>: Properties which are evaluated when they are first read (via a getter), then replace themselves with a regular data property. If they are set before they are read, they function exactly like a writable getter. This idea is the core of <a href="https://blissfuljs.com/docs#fn-lazy"><code>Bliss.lazy()</code></a>. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters">MDN mentions this pattern</a> too.</li>
</ul>
<p><em><strong>Note:</strong> Please don’t actually implement id/slug generation with <code>name.toLowerCase().replace(/\W+/g, "-")</code>. That’s very simplistic, to keep examples short. It privileges English/ASCII over other languages and writing systems, and thus, should be avoided.</em></p>
Mass function overloading: why and how?2021-02-10T00:00:00Zhttps://lea.verou.me/?p=3272<p>One of the things I’ve been doing for the past few months (on and off—more off than on TBH) is rewriting <a href="http://blissfuljs.com/">Bliss</a> to use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">ESM</a> <a href="https://lea.verou.me/2021/02/mass-function-overloading-why-and-how/#bliss-v2">1</a>. Since Bliss v1 was not using a modular architecture at all, this introduced some interesting challenges.
Bliss is essentially a collection of helper functions. Most of these functions have a number of different <a href="https://en.wikipedia.org/wiki/Type_signature#Method_signature">signatures</a>, to allow for more compact, readable code. The functions can be used for single things (one element, one set of arguments) or they can operate <em>en masse</em> (arrays of elements, object literals with multiple key-value pairs). As you might guess, this practice has been strongly inspired by the heavy use of <a href="https://en.wikipedia.org/wiki/Function_overloading">overloading</a> in jQuery, which was one of the driving factors behind its huge success.</p>
<p>For example, let’s take <a href="https://blissfuljs.com/docs.html#fn-style"><code>$.style()</code></a>. It can be used to set a single CSS property, on a single element, being a rather thin abstraction over <code>element.style</code>:</p>
<pre><code>$.style(element, "top", rect.top);
</code></pre>
<p>It can also be used to set a single CSS property on multiple elements:</p>
<pre><code>$.style($$(".popup"), "top", rect.top);
</code></pre>
<p>It can also be used to set multiple properties on a single element:</p>
<pre><code>$.style(element, {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left
);
</code></pre>
<p>Or to set multiple properties on multiple elements:</p>
<pre><code>$.style($$(".popup"), {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left
});
</code></pre>
<p>I’m a strong believer in overloading for handling both aggregate operations, as well as singular data. Supporting only aggregate operations would mean that developers have to pointlessly wrap single values in object literals or arrays. E.g. if <code>$.style()</code> only accepted arrays and object literals, our first example would be:</p>
<pre><code>$.style([element], {top: rect.top});
</code></pre>
<p>Not the end of the world, but certainly annoying and error-prone. Developers would often try setting the pair as separate arguments because it’s more natural, remember it doesn’t work, then adjust their code.</p>
<p>The opposite situation is much worse. If <code>$.style()</code> only supported singular operations, our last example would be:</p>
<pre><code>let values = {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left
};
for (let element of $$(".popup")) {
for (let property in values) {
$.style(element, property, values[property]);
}
}
</code></pre>
<p>Yikes! You don’t need a library for that! Just using <code>element.style</code> and <code>Object.assign()</code> would have actually fared better here:</p>
<pre><code>for (let element of $$(".popup")) {
Object.assign(element.style, {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left
});
}
</code></pre>
<p><code>$.style()</code> is not unique here: any Bliss function that accepts a main target element (the function’s <em>subject</em> as it’s called in the Bliss docs) also accepts arrays of elements. Similarly, any Bliss function that accepts key-value pairs as separate arguments, also accepts object literals with multiple of them.</p>
<p>In talks about API Design, I have presented this pattern (and overloading in general) as an instance of the <a href="https://en.wikipedia.org/wiki/Robustness_principle">Robustness principle</a> in action: <em>“Be liberal in what you accept”</em> is good practice for designing any user interface, and APIs are no exception. An analog in GUI design would be <a href="https://uxdesign.cc/the-bulk-experience-7fcca8080f82">bulk operations</a>: imagine if e.g. you could only delete emails one by one?</p>
<p>In JS, overloading is typically implemented by inspecting the types and number of a function’s arguments in the function, and branching accordingly. However, doing this individually on every function would get quite repetitive. Consider the following, <em>very</em> simplified implementation of <code>$.style()</code> with the overloading logic inlined:</p>
<pre><code>style(subject, ...args) {
if (Array.isArray(subject)) {
subject.forEach(e => style(e, ...args));
}
else if ($.type(args[0]) === "object" && args.length = 1) {
for (let p in args[0]) {
style(subject, p, args[0][p]);
}
}
else {
subject.style[args[0]] = args[1];
}
return subject;
}
</code></pre>
<p>Note that the actual code of this function is only 1 line out of the 13 lines of code it contains. The other 12 are just boilerplate for overloading. What a nightmare for maintainability and readability!</p>
<p>In Bliss v1, all functions were contained a single file, so they could be defined in their most singular version (one element, a single key-value pair as separate arguments etc), and the aggregate signatures could be automatically generated by looping over all defined functions and wrapping them accordingly.</p>
<p>However, in Bliss v2, each function is defined in its own module, as a default export. There is also a module pulling them all together and adding them on <code>$</code>, but people <em>should</em> be able to do things like:</p>
<pre><code>import style from "https://v2.blissfuljs.com/src/dom/style.js";
</code></pre>
<p>And <code>style()</code> would need to support its full functionality, not be some cut down version allowing only single elements and one property-value pair. What use would that be?</p>
<p>This means that the overloading needs to happen in the module defining each function. It cannot happen via a loop in the <code>index.js</code> module. How can we do this and still keep our code maintainable, short, and easy to change? I explored several alternatives.</p>
<p><em>(We are not going to discuss the implementation of <code>overload()</code> in each case below, but if you’re interested in the current one, it’s <a href="https://github.com/LeaVerou/bliss/blob/v2/src/overload.js">on Github</a>. Do note that just like everything in Bliss v2, it’s subject to heavy change before release)</em></p>
<h4 id="option-1%3A-inside-each-function" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/02/mass-function-overloading-why-and-how/#option-1%3A-inside-each-function">Option 1: Inside each function</a></h4>
<pre><code>export default function style(subject, ...args) {
return overload(subject, args, (element, property, value) => {
element.style[property] = value;
})
}
</code></pre>
<p>While this at first seems like the most natural way to abstract the inlined code we previously had, it’s the most verbose and hard to read. Furthermore, it adds extra code that needs to be executed every time the function is called and needs us to pass the current execution context through. It’s far better to go with a solution that takes the singular function as input, and gives you a modified function that just works. That’s what the next two options use.</p>
<h4 id="option-2%3A-wrap-with-overload()" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/02/mass-function-overloading-why-and-how/#option-2%3A-wrap-with-overload()">Option 2: Wrap with overload()</a></h4>
<pre><code>export default overload(function style(element, property, value) {
element.style[property] = value;
});
</code></pre>
<h4 id="option-3%3A-overload-at-export" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/02/mass-function-overloading-why-and-how/#option-3%3A-overload-at-export">Option 3: Overload at export</a></h4>
<pre><code>function style(element, property, value) {
element.style[property] = value;
}
export default overload(style);
</code></pre>
<p>Options 2 and 3 are very similar. I was originally inclined to go with 2 to avoid typing the function name twice, but I eventually concluded that it made the code harder to read, so I went with option 3: Declaring the function, then overloading it & exporting it.</p>
<p>I wasn’t super happy with any of these options. Something inside me protested the idea of having to include even a line of boilerplate in every single module, and almost every Bliss function depending on another module. However, in the large scheme of things, I think this boilerplate is as minimal as it gets, and certainly beats the alternatives.</p>
<p>Have you had to perform a transform on a number of different modules in your code? How did you abstract it away?</p>
<p>1 You can see the rewrite progress in the <a href="https://github.com/LeaVerou/bliss/tree/v2">v2 branch on Github</a>, and even use <a href="https://v2.blissfuljs.com/">v2.blissfuljs.com</a> to import modules from and experiment. Note that at the time of writing, all of the progress is in the code, the docs and tests are still all about v1.</p>
Dark mode in 5 minutes, with inverted lightness variables2021-03-30T00:00:00Zhttps://lea.verou.me/?p=3293<p>By now, you probably know that you can use custom properties for individual color components, to avoid repeating the same color coordinates multiple times throughout your theme. You may even know that you can use the same variable for multiple components, e.g. HSL hue and lightness:</p>
<pre><code class="language-css">:root {
--primary-hs: 250 30%;
}
h1 {
color: hsl(var(--primary-hs) 30%);
}
article {
background: hsl(var(--primary-hs) 90%);
}
article h2 {
background: hsl(var(--primary-hs) 40%);
color: white;
}
</code></pre>
<p>Here is a <a href="https://codepen.io/leaverou/pen/QWdjpEL">very simple page designed with this technque</a>:</p>
<iframe id="cp_embed_QWdjpEL" src="https://codepen.io/anon/embed/QWdjpEL?height=500&theme-id=1&slug-hash=QWdjpEL&default-tab=result" height="500" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed QWdjpEL" title="CodePen Embed QWdjpEL" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>Unlike preprocessor variables, you could even locally override the variable, <a href="https://codepen.io/leaverou/pen/rNjOypm">to have blocks with a different accent color</a>:</p>
<pre><code>:root {
--primary-hs: 250 30%;
--secondary-hs: 190 40%;
}
article {
background: hsl(var(--primary-hs) 90%);
}
article.alt {
--primary-hs: var(--secondary-hs);
}
</code></pre>
<iframe id="cp_embed_rNjOypm" src="https://codepen.io/anon/embed/rNjOypm?height=500&theme-id=1&slug-hash=rNjOypm&default-tab=result" height="500" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed rNjOypm" title="CodePen Embed rNjOypm" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>This is all fine and dandy, until dark mode comes into play. The idea of using custom properties to make it easier to adapt a theme to dark mode is not new. However, <a href="https://alxgbsn.co.uk/2019/02/08/blog-theming-css-variables/">in</a> <a href="https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web/">every</a> <a href="https://dev.to/ananyaneogi/create-a-dark-light-mode-switch-with-css-variables-34l8">article</a> <a href="https://css-irl.info/quick-and-easy-dark-mode-with-css-custom-properties/">I</a> <a href="https://piccalil.li/tutorial/create-a-user-controlled-dark-or-light-mode">have</a> <a href="https://www.digitalocean.com/community/tutorials/css-theming-custom-properties">seen</a>, the strategy suggested is to create a bunch of custom properties, one for each color, and override them in a media query.</p>
<p>This is a fine approach, and you’ll likely want to do that for at least part of your colors eventually. However, even in the most disciplined of designs, not every color is a CSS variable. You often have colors declared inline, especially grays (e.g. the footer color in our example). This means that adding a dark mode is taxing enough that you may put it off for later, especially on side projects.</p>
<p>The trick I’m going to show you will make anyone who knows enough about color cringe (sorry Chris!) but it does help you create a dark mode that <em>works</em> in minutes. It won’t be great, and you should eventually tweak it to create a proper dark mode (also <a href="https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web/">dark mode is not just about swapping colors</a>) but it’s better than nothing and can serve as a base.
The basic idea is to use custom properties for the <em>lightness</em> of colors instead of the entire color. Then, in dark mode, you override these variables with <code>100% - lightness</code>. This generally produces light colors for dark colors, medium colors for medium colors, and dark colors for light colors, and still allows you to define colors inline, instead of forcing you to use a variable for every single color. This is what the code would look like for our example:</p>
<pre><code>root {
--primary-hs: 250 30%;
--secondary-hs: 190 40%;
--l-0: 0%;
--l-30: 30%;
--l-40: 40%;
--l-50: 50%;
--l-90: 90%;
--l-100: 100%;
}
@media (prefers-color-scheme: dark) {
:root {
--l-0: 100%;
--l-30: 70%;
--l-40: 60%;
--l-90: 10%;
--l-100: 0%;
}
}
body {
background: hsl(0 0% var(--l-100));
color: hsl(0 0% var(--l-0));
}
h1 {
color: hsl(var(--primary-hs) var(--l-30));
}
article {
background: hsl(var(--primary-hs) var(--l-90));
}
article h2 {
background: hsl(var(--primary-hs) 40%);
color: white;
}
footer {
color: hsl(0 0% var(--l-40));
}
</code></pre>
<iframe id="cp_embed_eYgJyav" src="https://codepen.io/anon/embed/eYgJyav?height=500&theme-id=1&slug-hash=eYgJyav&default-tab=result" height="500" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed eYgJyav" title="CodePen Embed eYgJyav" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>The result looks like this in light & dark mode:</p>
<figure>
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/hsl-normal.png" alt="" />
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/hsl-dm.png" alt="" />
<figcaption>
The light mode we designed and the auto-generated dark mode, side by side
</figcaption>
</figure>
<p>Note that here we indiscriminately replaced all lightnesses with lightness variables. In reality, we don’t need to be quite as sweeping. For example, the article titles would actually look better and would have better contrast if we just kept them the same:</p>
<figure>
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/hsl-dm.png" alt="" />
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/hsl-dm-exception.png" alt="" />
<figcaption>
<p>Comparison of dark mode with every lightness becoming a variable versus a more refined approach, where we make exceptions as needed (in this case the background and text colors for <code>article > h2</code>).
</p></figcaption><p></p>
</figure>
<p>These are decisions that are easy to make while you go through your CSS replacing lightness percentages with variables and previewing the result.</p>
<h2 id="the-problem-with-hsl" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/03/inverted-lightness-variables/#the-problem-with-hsl">The problem with HSL</a></h2>
<p>But <em>why</em> were the article headers easier to read with their original colors than with inverted lightness? The root cause is that HSL lightness does not actually correspond to what humans perceive as lightness, and the same lightness difference can produce vastly different perceptual differences.</p>
<p>That is the big problem with this approach: it assumes that HSL lightness actually means something, but <a href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#3-lch-lightness-actually-means-something">as we’ve discussed before</a>, it does not. Yellow and blue have the same HSL lightness (50%) for crying out loud! Also, you will notice that your dark colors have smaller differences between them than your light colors, because <a href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/#2-lch-and-lab-is-perceptually-uniform">HSL is not perceptually uniform</a>.</p>
<p>Does that mean the technique is not useful for anything other than a placeholder while we develop our <em>real</em> dark mode, if that?</p>
<p>Well, things are not <em>quite</em> as grim.</p>
<p>Soon enough, we will get <a href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/">LCH colors</a> in the browser. The first browser implementation <a href="https://webkit.org/blog/11548/release-notes-for-safari-technology-preview-120/#post-11548:~:text=Added%20support%20for%20lab()%2C%20lch()%2C%20and%20color(lab%20...)%20colors">just recently shipped in Safari</a> and there is activity in that space among the other browser vendors too.</p>
<p>LCH is a much better color space for this technique, because its lightness actually means something, not just across different lightnesses of the same color, but across different hues and chromas.</p>
<p>This next example needs Safari TP 120+ . Compare these two gradients, the top one showing various HSL colors all with lightness 50%, and the bottom various LCH colors, all with lightness 50%. You can even adjust the slider and try different lightnesses:</p>
<iframe id="cp_embed_xxgOZQR" src="https://codepen.io/anon/embed/xxgOZQR?height=400&theme-id=1&slug-hash=xxgOZQR&default-tab=result" height="400" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed xxgOZQR" title="CodePen Embed xxgOZQR" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>Here is a screenshot for those of you who don’t have access to Safari TP 120+:</p>
<p><img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/image.png" alt="" /></p>
<p>Notice that in HSL, some colors (like yellow and cyan) are much lighter than others. In LCH, all colors at the same lightness are, well, the same lightness.</p>
<p>Keep in mind that LCH chroma doesn’t really correspond to HSL lightness, so even though we’ve set it to the same number, it doesn’t correspond to the same thing.</p>
<p>So, how would this technique work with LCH colors? Let’s try it out!</p>
<p>I used <a href="https://css.land/lch/">this tool</a> to convert the existing HSL colors to LCH, then tweaked the values manually a bit as the initially converted colors didn’t look nice across all LCH lightnesses (note that HSL colors with the same hue and saturation may have different hue and chromas in LCH. The opposite would defeat the point!). This is what this technique looks like with LCH colors instead (you will need Safari TP 120 or later to view this):</p>
<iframe id="cp_embed_df41d39c06f1b477bd00d57ce9c853ee" src="https://codepen.io/anon/embed/df41d39c06f1b477bd00d57ce9c853ee?height=500&theme-id=1&slug-hash=df41d39c06f1b477bd00d57ce9c853ee&default-tab=result" height="500" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed df41d39c06f1b477bd00d57ce9c853ee" title="CodePen Embed df41d39c06f1b477bd00d57ce9c853ee" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>And here is a screenshot:</p>
<figure>
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/lch-normal.png" alt="" />
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/lch-dm.png" alt="" />
<figcaption>
Light mode and auto-generated dark mode via inverted lightness variables in LCH.
</figcaption>
</figure>
<p>Not only does dark mode look a lot better, but even in light mode, our two alternate colors actually look more uniform since they have the same LCH lightness.</p>
<p>Here is a comparison of the two dark modes:</p>
<figure>
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/hsl-dm.png" alt="" />
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/lch-dm.png" alt="" />
<figcaption>
Light mode and auto-generated dark mode via inverted lightness variables in LCH.
</figcaption>
</figure>
<p>Comparison of the two auto-generated dark modes, via HSL lightness on the left and LCH lightness on the right.</p>
<p>Here you can see an animated comparison of them over each other:</p>
<figure class="image-comparison">
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/hsl-dm.png" alt="" />
<img src="https://lea.verou.me/2021/03/inverted-lightness-variables/images/lch-dm.png" alt="" />
</figure>
<p>Note that in reality, until LCH colors are reliably supported everywhere you’d need to provide a fallback via <code>@supports</code>, but for brevity, I did not include one in this demo.</p>
<h2 id="automating-generation-of-lightness-variables" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/03/inverted-lightness-variables/#automating-generation-of-lightness-variables">Automating generation of lightness variables</a></h2>
<p>If you are using a preprocessor that supports loops, such as Sass, you can automate the generation of these variables, and make them even more granular, e.g. every 5%:</p>
<pre><code>:root {
@for $i from 0 through 20 {
--l-#{$i * 5}: #{$i * 5}%;
}
}
@media (prefers-color-scheme: dark) {
:root {
@for $i from 0 through 20 {
--l-#{$i * 5}: #{100 - $i * 5}%;
}
}
}
</code></pre>
<h2 id="can-we-make-lightness-variables-more-dry%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/03/inverted-lightness-variables/#can-we-make-lightness-variables-more-dry%3F">Can we make lightness variables more DRY?</a></h2>
<p>Some of you may have disliked the repetition of values: we need to declare e.g. <code>--l-40</code> as 40%, then set it to 60% in dark mode. Can’t we derive it somehow, by subtracting the value we already have from 100%?</p>
<p>Those with experience in programming may try something like this:</p>
<pre><code>--l-40: calc(100% - var(--l-40));
</code></pre>
<p>However, this will not work. CSS is not an imperative language. It does not have steps of calculation, where variables have different values before and after each step. There is no such concept of time, all declarations that are currently applied, need to be true at once. It’s more similar to the reactive evaluation of spreadsheet formulas than to computation in JS and other popular programming languages (there are general purpose <a href="https://en.wikipedia.org/wiki/Reactive_programming">reactive programming</a> languages, but they are less well known). Therefore, declarations like the one above are considered cycles: since <code>--l-40</code> cannot refer to itself, this is an error, and <a href="https://drafts.csswg.org/css-variables/#cycles"><code>--l-40</code> would be set to its initial value</a> as an error recovery mechanism (since CSS cannot throw errors).</p>
<p>So, is there a way to avoid declaring lightness variables twice, once for light mode and once for dark mode?</p>
<p>There <em>is</em>, but I wouldn’t recommend it. It makes the code more convoluted to read and comprehend, for little benefit. But for the sake of intellectual amusement, I’m going to describe it here.</p>
<p>Instead of setting <code>--l-40</code> to 40%, we are going to set it in terms of its <em>difference from 50%</em>, i.e. <code>-10%</code>. Then, <code>calc(50% + var(--l-40))</code> gives us 40% and <code>calc(50% - var(--l-40))</code> gives us 60%, the two values we need. We can therefore declare one variable that is <code>-1</code> in dark mode and <code>1</code> in light mode, and just multiply with that.</p>
<p>Here is a subset of what our code would be like with this:</p>
<pre><code>:root {
--dm: 1;
/* Example declaration: */
--l-40: -10%;
}
@media (prefers-color-scheme: dark) {
:root {
--dm: -1;
}
}
/* Example usage: */
footer {
color: hsl(0 0% calc(50% + var(--dm) * var(--l-40));
/* Ewww! */
}
</code></pre>
<p>And hopefully now you can see why I wouldn’t recommend this: it makes <strong>usage</strong> much more complicated, to DRY up a few declarations that would only be specified once. It’s this kind of obsessive adherence to DRY that <a href="https://lea.verou.me/2020/09/developer-priorities-throughout-their-career/">programmers eventually realize is counterproductive</a>.</p>
<hr />
<p><em>Liked this article? Sign up for my <a href="https://smashingconf.com/online-workshops/workshops/lea-verou">Smashing Workshop on Dynamic CSS</a> for more content like this!</em></p>
82% of developers get this 3 line CSS quiz wrong2021-05-21T00:00:00Zhttps://lea.verou.me/?p=3347<p>(<em>I always wanted to do a clickbait title like this and when this chance came along I could not pass it up. 😅 Sorry!</em>)</p>
<p>While putting my ideas into slides for my <a href="https://smashingconf.com/online-workshops/workshops/lea-verou">Dynamic CSS workshop</a> for next week, I was working on a slide explaining how the CSS wide keywords work with custom properties. <code>inherit</code>, <code>initial</code>, <code>unset</code> I had used numerous times and knew well. But what about <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/revert"><code>revert</code></a>? How did that work? I had an idea, but quickly coded up a demo to try it out.</p>
<p>The code was:</p>
<pre><code>:root {
--accent-color: skyblue;
}
div {
--accent-color: revert;
background: var(--accent-color, orange);
}
</code></pre>
<p>Phew, I was correct, but the amount of uncertainty I had before seeing the result tipped me that I might be on to something.</p>
<p>Before you read on, take a moment to think about what you would vote. Warning: Spoilers ahead!</p>
<p>🤔</p>
<p>🤔</p>
<p>🤔</p>
<p>🤔
So I posted a quiz on Twitter:</p>
<p><a href="https://twitter.com/LeaVerou/status/1395379573190168576">https://twitter.com/LeaVerou/status/1395379573190168576</a></p>
<p>These were the results after the 24 hours it ran for:</p>
<p><img src="https://lea.verou.me/2021/05/82-of-developers-get-this-3-line-css-quiz-wrong/images/image.png" alt="" /></p>
<p><code>orange</code> was the clear winner, and the actual correct answer, <code>skyblue</code> only got 18.1%, nearly the same as <code>transparent</code>!</p>
<p>If you got it wrong, you’re in <strong>very</strong> good company: not only did 82% of poll respondents get it wrong as well, but even the editor of the <a href="https://drafts.csswg.org/css-variables/">CSS Variables spec</a> and co-editor of <a href="https://drafts.csswg.org/css-cascade/#default">CSS Cascading and Inheritance</a> (which defines <code>revert</code>), <a href="https://xanthir.com/">Tab Atkins</a>, told me privately that he got it wrong too: he voted for <code>orange</code>! <em>(Yes, I did get his permission to mention this)</em></p>
<p>So what actually happens? Why do we get <code>skyblue</code>? I will try to explain as best as I can.</p>
<p>Let’s start by what <code>revert</code> does: It reverts the cascaded value of the property from its current value to the value the property <em>would have had</em> if no changes had been made by the current style origin to the current element.</p>
<p>This means it cancels out any author styles, and resets back to whatever value the property would have from the user stylesheet and UA stylesheet. Assuming there is no <code>--accent-color</code> declaration in the user stylesheet, and of course UA stylesheets don’t set custom properties, then that means the property doesn’t have a value.</p>
<p>Since custom properties are inherited properties (unless they are <a href="https://drafts.css-houdini.org/css-properties-values-api-1/#registered-custom-property">registered</a> with <code>inherits: false</code>, but this one is not), this means the inherited value trickles in, which is — you guessed it — <code>skyblue</code>. You can see for yourself in <a href="https://codepen.io/leaverou/pen/zYZZpaY?editors=1100">this codepen</a>.</p>
<iframe id="cp_embed_zYZZpaY" src="https://codepen.io/anon/embed/zYZZpaY?height=250&theme-id=1&slug-hash=zYZZpaY&default-tab=result" height="250" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed zYZZpaY" title="CodePen Embed zYZZpaY" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>What if our property were registered as non-inheriting? Would it then be <code>orange</code>? Nice try, but no. When we register a custom property, it is mandatory to provide an initial value. This means that the property always resolves to a value, even <code>--accent-color: initial</code> does not trigger the fallback anymore. You can see this for yourself in <a href="https://codepen.io/leaverou/pen/qBrrpKZ?editors=1100">this codepen</a> (<a href="https://caniuse.com/mdn-css_at-rules_property">Chrome only as of May 2021</a>).</p>
<iframe id="cp_embed_qBrrpKZ" src="https://codepen.io/anon/embed/qBrrpKZ?height=250&theme-id=1&slug-hash=qBrrpKZ&default-tab=result" height="250" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed qBrrpKZ" title="CodePen Embed qBrrpKZ" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p><em>Liked this? Then you will love <a href="https://smashingconf.com/online-workshops/workshops/lea-verou">the workshop</a>! There are <a href="https://smashingconf.com/online-workshops/workshops/lea-verou">still a few tickets left</a>!</em></p>
Is the current tab active?2021-05-24T00:00:00Zhttps://lea.verou.me/?p=3357<p>Today I ran into an interesting problem. Interesting because it’s one of those very straightforward, deceptively simple questions, that after a fair amount of digging, does not appear to have a definite answer (though I would love to be wrong!).</p>
<p>The problem was to determine <strong>if the current tab is active</strong>. Yes, as simple as that.</p>
<h2 id="why%3F-(i.e.-my-use-case)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/05/is-the-current-tab-active/#why%3F-(i.e.-my-use-case)">Why? (i.e. my use case)</a></h2>
<p>I was working on my slide deck framework, <a href="https://github.com/leaverou/inspire.js">Inspire.js</a>. There is a <a href="https://github.com/LeaVerou/inspire.js/tree/master/plugins/presenter">presenter mode plugin</a>, which spawns a new window with your slides (“projector view”), whereas your current window becomes a “presenter view”, with open notes, preview of the next slide, optional progress indicator for time etc.</p>
<p>However, this plugin was <em>not very good</em>. The two windows are synced, but only if you use presenter view to navigate slides. If you use the projector view to advance slides, the syncing breaks. Why would you use the projector mode? Many reasons, e.g. to interact with a live demo, or even play a video. If you have a live demo heavy presentation, you may even want to mirror your screen and only ever interact with the projector mode, while having the presenter mode on a secondary screen, just to look at.</p>
<p>The way the plugin worked was that every time the slide changed in the presenter view, it propagated the change in the projector view. To make the syncing bidirectional, it would be good to know if the current window is the active tab, and if so, propagate all slide navigation to the other one, regardless of which one is the projector view and which one is the presenter view.</p>
<p>And this, my friends, is how I ended up in this rabbit hole.</p>
<p><em>(Yes, there are other solutions to this particular problem. I could just always propagate regardless and have checks in place to avoid infinite loops. But that’s beside the point.)</em></p>
<h2 id="what-about-the-visibility-api%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/05/is-the-current-tab-active/#what-about-the-visibility-api%3F">What about the Visibility API?</a></h2>
<p>In most resources around the Web, people were rejoicing about how the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API">Visibility API</a> makes this problem trivial. “Just use <code>document.hidden</code>!” people would gleefully recommend to others.</p>
<p>Yes, the Visibility API is great, when you want to determine whether the current tab is <em>visible</em>. That is <strong>not</strong> the same as whether it is <em>active</em>.</p>
<p>You may have two windows side by side, both visible, but only one of them is active. You may even have a window entirely obscuring another window, but you can still tab through to it and make it active. Active and visible are entirely orthogonal states, which are only loosely correlated.</p>
<p>In my use case, given that both the projector view and presenter view would be visible at all times, this is a no-go that doesn’t even solve a subset of use cases.</p>
<h2 id="what-about-focus-and-blur-events-on-window%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/05/is-the-current-tab-active/#what-about-focus-and-blur-events-on-window%3F">What about focus and blur events on window?</a></h2>
<p>The other solution that was heavily recommended was using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event"><code>focus</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/blur_event"><code>blur</code></a> events on <code>window</code>. This does get us partway there. Indeed, when the current tab <em>becomes</em> active, the <code>focus</code> event fires. When another tab becomes active, the <code>blur</code> event fires.</p>
<p>Notice the emphasis on “becomes”. <strong>Events notify us about a state change, but they are no help for determining the <em>current state</em>.</strong> If we get a <code>focus</code> or <code>blur</code> event, we know whether our tab is active or not, but if we don’t get any, we simply don’t know. A tab can start off as active or not, and there is no way to tell.</p>
<p><em>How can a tab possibly start off as inactive</em>? One easy way to reproduce this is to hit Return on the address bar and immediately switch to another window. The tab you just loaded just starts off as inactive and no <code>blur</code> event is ever fired.</p>
<h2 id="what-about-document.activeelement%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/05/is-the-current-tab-active/#what-about-document.activeelement%3F">What about document.activeElement?</a></h2>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement"><code>document.activeElement</code></a> property will always return the currently focused element in a page. Can we use it to determine if a window currently has focus? Nope, cause that would be too easy.</p>
<p>Run <code>setTimeout(() => console.log(document.activeElement), 2000)</code> in the console and quickly switch windows. Return >2 seconds later and see what was logged. It’s the <code><body></code> element!</p>
<p>Wait, maybe we can assume that if the currently focused element is a <code><body></code> element then the current window is inactive? Nope, you get the same result in an active tab, if you simply haven’t focused anywhere.</p>
<h2 id="what-about-document.hasfocus()%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/05/is-the-current-tab-active/#what-about-document.hasfocus()%3F">What about document.hasFocus()?</a></h2>
<p>When I discovered <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus"><code>document.hasFocus()</code></a> I thought that was the end of it. Surely, this is exactly what I need?!? <a href="https://html.spec.whatwg.org/multipage/interaction.html#dom-document-hasfocus">The spec</a> made it sound so promising. I quickly switched to my <a href="about:blank">about:blank</a> tab that I use for trying things out, and ran it in the console.</p>
<pre><code>> document.hasFocus()
< false
</code></pre>
<p>🤦🏽♀️🤦🏽♀️🤦🏽♀️</p>
<p>Neeeext!</p>
<p><strong>Edit:</strong> <code>document.hasFocus()</code> may be the solution after all! As <a href="https://twitter.com/_AlK/status/1396800328088133633">pointed</a> <a href="https://twitter.com/outofroutine/status/1396800341648318472">out</a> <a href="https://twitter.com/jaffathecake/status/1396802975151054849">to me</a> on Twitter, the problem above was that unlike I did with <code>document.activeElement</code>, I ran this synchronously in the console and it returned <code>false</code> because the console as the active window. An asynchronous log while I make sure the actual window is focused would do the trick.</p>
<h2 id="the-anti-climactic-conclusion" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/05/is-the-current-tab-active/#the-anti-climactic-conclusion">The anti-climactic conclusion</a></h2>
<p><strong>Edit:</strong> I left this section in because the moral is still valid for other cases, but it looks like <code>document.hasFocus()</code> was the solution after all.</p>
<p>If you’re expecting this to end with a revelation of an amazing API that I had originally missed and addresses this, you will be disappointed. If there is such a silver bullet, I did not find it. Maybe someone will point it out to me after publishing this blog post, in which case I will update it so that you don’t struggle like I did.</p>
<p>But in my case, I simply gave up trying to find a general solution. Instead, I took advantage of the knowledge my code had in this specific situation: I knew what the other window was, and I primarily cared which one of the two (if any) had focus.</p>
<pre><code>// Track whether presenter or projector is the active window
addEventListener("focus", _ => {
Inspire.isActive = true;
// If this window is focused, no other can be
if (Inspire.projector) {
Inspire.projector.Inspire.isActive = false;
}
else if (Inspire.presenter) {
Inspire.presenter.Inspire.isActive = false;
}
});
addEventListener("blur", _ => {
Inspire.isActive = false;
// If this window is not focused,
// we cannot make assumptions about which one is.
});
</code></pre>
<p>Given that the presenter view calls <code>window.focus()</code> after opening the projector view, in practice this was pretty bulletproof.</p>
<p>What’s the moral of this story?</p>
<ul>
<li>Sometimes simple questions do not have a good answer when it comes to the Web Platform</li>
<li>If your code cannot answer the general question correctly in all cases, maybe it can answer a specific one that solves your particular problem, even if that leads to a less elegant solution.</li>
</ul>
<p>That’s it folks.</p>
Inherit ancestor font-size, for fun and profit2021-06-24T00:00:00Zhttps://lea.verou.me/?p=3366<p>If you’ve been writing CSS for any length of time, you’re probably familiar with the <code>em</code> unit, and possibly the other <a href="https://drafts.csswg.org/css-values/#font-relative-lengths">type-relative units</a>. We are going to refer to <code>em</code> for the rest of this post, but anything described works for all type-relative units.</p>
<p>As you well know, <code>em</code> resolves to the current font size on all properties except <code>font-size</code>, where it resolves to the <em>parent</em> font size. It can be quite useful for making scalable components that adapt to their context size.</p>
<p>However, I have often come across cases where you actually need to “circumvent” one level of this. Either you need to set <code>font-size</code> to the grandparent font size instead of the parent one, or you need to set other properties to the parent font size, not the current one.</p>
<p><em>If you’re already familiar with the problem and just want <a href="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/#the-solution">the solution</a>, <a href="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/#the-solution">skip ahead</a>. The next few paragraphs are for those thinking “but when would you ever need this?”</em></p>
<p>Sometimes, there are workarounds, and it’s just a matter of keeping DRY. For example, take a look at this speech bubble:</p>
<iframe id="cp_embed_dyvxGLo" src="https://codepen.io/anon/embed/dyvxGLo?height=410&theme-id=1&slug-hash=dyvxGLo&default-tab=result" height="410" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed dyvxGLo" title="CodePen Embed dyvxGLo" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>Note this in the CSS:</p>
<pre><code>/* This needs to change every time the font-size changes: */
top: calc(100% + 1em / 2.5);
font-size: 250%;
</code></pre>
<p>Note that every time we change the font size we also need to adjust <code>top</code>. And ok, when they’re both defined in the same rule we can just delegate this to a variable:</p>
<pre><code>--m: 2.5;
top: calc(100% + 1em / var(--m));
font-size: calc(var(--m) * 100%);
</code></pre>
<p>However, in the general case the font size may be defined elsewhere. For example, a third party author may want to override the emoji size, they shouldn’t also need to override anything else, our CSS should just adapt.</p>
<p>In other cases, it is simply not possible to multiply and divide by a factor and restore the ancestor font size. Most notably, when the current (or parent) font-size is set to <code>0</code> and we need to recover what it was one level up.</p>
<p>I’ve come across many instances of this in the 16 years I’ve been writing CSS. Admittedly, there were way more use cases pre-Flexbox and friends, but it’s still useful, as we will see. In fact, it was the latest one that prompted this post.</p>
<p>I needed to wrap <code><option></code> elements by a generic container for a library I’m working on. Let me stop you there, no, I could not just set classes on the options, I needed an actual container in the DOM.</p>
<p>As you can see in <a href="https://codepen.io/leaverou/pen/OJpexzE">this pen</a>, neither <code><div></code> nor custom elements work here: when included in the markup they are just discarded by the parser, and when inserted via script they are in the DOM, but the options they contain are not visible. The only <a href="https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element">elements that work inside a <code><select></code></a> are: <code><option></code>, <code><optgroup></code>, and <a href="https://html.spec.whatwg.org/multipage/dom.html#script-supporting-elements-2">script-supporting elements</a> (currently <code><template></code> and <code><script></code>). Except <code><optgroup></code>, none of the rest renders any contents and thus, is not fit for my use case. It had to be <code><optgroup></code>, sadly.</p>
<p>However, using <code><optgroup></code>, even without a <code>label</code> attribute inserts an ugly gap in the select menu, where the label would have gone (<a href="https://codepen.io/leaverou/pen/vYxoGpZ">pen</a>):</p>
<p><img src="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/images/image-3.png" alt="" /></p>
<p>(There were also gaps on the left of the labels, but we applied some CSS to remove them)</p>
<p>There appears to be <a href="https://www.google.com/search?q=remove+optgroup+label+site:stackoverflow.com">no way to remove</a> said gap.</p>
<p>Ideally, this should be fixed on the user agent level: Browsers should not generate a label box when there is no label attribute. However, I needed a solution now, not in the far future. There was no pseudo-element for targeting the generated label. The only solution that worked was along these lines ([pen](optgroup:not([label]) {
display: contents;
font-size: 0;
}</p>
<div></div>
optgroup:not([label])> * {
font-size: 13.333px;
})):
<pre><code>optgroup:not([label]) {
font-size: 0;
}
optgroup:not([label]) > * {
font-size: 13.333px;
}
</code></pre>
<p>The weird <code>13.333px</code> value was taken directly from the Chrome UA stylesheet (as inspected). However, it is obviously flimsy, and will break any additional author styling. It would be far better if we could say “give me whatever <code>1em</code> is on the grandparent”. Can we?</p>
<h2 id="the-solution" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/#the-solution">The solution</a></h2>
<p>What if we could use custom properties to solve this? Our first attempt might look something like this:</p>
<pre><code>select {
--em: 1em;
}
optgroup:not([label]) {
font-size: 0;
}
optgroup:not([label]) > * {
font-size: var(--em);
}
</code></pre>
<p>However this is <a href="https://codepen.io/leaverou/pen/dyvxXyV">horribly broken</a>:</p>
<p><img src="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/images/image-1.png" alt="" /></p>
<p>All the options have disappeared!!</p>
<p>What on Earth happened here?!</p>
<p>By default, custom properties are just containers for CSS tokens.When they inherit, they inherit as specified, with only any <code>var()</code> references substituted and no other processing. This means that the <code>1em</code> we specified inherits as the <code>1em</code> <em>token</em>, not as whatever absolute length it happens to resolve to on <code>select</code>. It only becomes an absolute length at the point of usage, and this is whatever <code>1em</code> would be there, i.e. <code>0</code>. So all our options disappeared because we set their font size to <code>0</code>!</p>
<p>If only we could make <code>1em</code> resolve to an actual absolute length at the point of declaration and inherit as that, just like native properties that accept lengths?</p>
<p>Well, you’re in luck, because today we can!</p>
<p>You may be familiar with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property"><code>@property</code> rule</a> as “the thing that allows us to animate custom properties”. However, it is useful for so much more than that.</p>
<p>If we register our custom property as a <code><length></code>, this makes the <code>1em</code> resolve on the element we specified it on, and inherit as an absolute length! <a href="https://codepen.io/leaverou/pen/KKWOMMK">Let’s try this</a>:</p>
<pre><code>@property --em {
syntax: "<length>";
initial-value: 0;
inherits: true;
}
select {
--em: 1em;
}
optgroup:not([label]) {
display: contents;
font-size: 0;
}
optgroup:not([label]) > * {
font-size: var(--em);
}
/* Remove Chrome gap */
:where(optgroup:not([label]) > option)::before {
content: "";
}
</code></pre>
<iframe id="cp_embed_KKWOMMK" src="https://codepen.io/anon/embed/KKWOMMK?height=250&theme-id=1&slug-hash=KKWOMMK&default-tab=css,result" height="250" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed KKWOMMK" title="CodePen Embed KKWOMMK" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>And here is the same technique used for the speech bubble:</p>
<iframe id="cp_embed_XWMZewB" src="https://codepen.io/anon/embed/XWMZewB?height=250&theme-id=1&slug-hash=XWMZewB&default-tab=result" height="250" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed XWMZewB" title="CodePen Embed XWMZewB" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<h2 id="fallback" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/#fallback">Fallback</a></h2>
<p>This is all fine and dandy for the <a href="https://caniuse.com/mdn-css_at-rules_property">68% (as of June 2021) of users that are using a browser that supports <code>@property</code></a>, but what happens in the remaining 32%? It’s not pretty:</p>
<p><img src="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/images/image-1.png" alt="" /></p>
<p>We get the default behavior of an unregistered property, and thus none of our options show up! This is <em>bad</em>.</p>
<p>We should clearly either provide a fallback or conditionally apply these rules only in browsers that support <code>@property</code>.</p>
<p>We can easily detect <code>@property</code> support in JS and add a class to our root element:</p>
<pre><code>if (window.CSSPropertyRule) {
let root = document.documentElement;
root.classList.add("supports-atproperty");
}
</code></pre>
<p>Then we can just use the descendant combinator:</p>
<pre><code>:root.supports-atproperty optgroup:not([label]) {
font-size: 0;
}
</code></pre>
<h2 id="css-only-fallback-for-%40property" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/#css-only-fallback-for-%40property">CSS-only fallback for @property</a></h2>
<p>While the JS fallback works great, I couldn’t help but wonder if there’s a CSS only way.</p>
<p>My first thought was to use <code>@supports</code>:</p>
<pre><code>@supports (--em: flugelhorn) {
/* Does not support @property */
}
</code></pre>
<p>The theory was, if a browser supported any value to be assigned on a property registered as a <code><length></code>, surely it does not support property registration.</p>
<p>It turns out, registered properties do not validate their syntax at parse time, and thus are always valid for <code>@supports</code>. This is <a href="https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-custom-properties">explained in the spec</a>:</p>
<blockquote>
<p>When parsing a page’s CSS, UAs commonly make a number of optimizations to help with both speed and memory.</p>
<p>One of those optimizations is that they only store the properties that will actually have an effect; they throw away invalid properties, and if you write the same property multiple times in a single declaration block, all but the last valid one will be thrown away. (This is an important part of CSS’s error-recovery and forward-compatibility behavior.)</p>
<p>This works fine if the syntax of a property never changes over the lifetime of a page. If a custom property is registered, however, it can change its syntax, so that a property that was previously invalid suddenly becomes valid.</p>
<p>The only ways to handle this are to either store every declaration, even those that were initially invalid (increasing the memory cost of pages), or to re-parse the entire page’s CSS with the new syntax rules (increasing the processing cost of registering a custom property). Neither of these are very desirable.</p>
<p>Further, UA-defined properties have their syntax determined by the version of the UA the user is viewing the page with; this is out of the page author’s control, which is the entire reason for CSS’s error-recovery behavior and the practice of writing multiple declarations for varying levels of support. A custom property, on the other hand, has its syntax controlled by the page author, according to whatever stylesheet or script they’ve included in the page; there’s no unpredictability to be managed. Throwing away syntax-violating custom properties would thus only be, at best, a convenience for the page author, not a necessity like for UA-defined properties.</p>
</blockquote>
<p>Ok this is great, and totally makes sense, but what can we do? How can we provide a fallback?</p>
<p>It turns out that there is a way, but brace yourself, as it’s quite hacky. I’m only going to describe it for entertainment purposes, but I think for real usage, the JS way is far more straightforward, and it’s the one I’ll be using myself.</p>
<p>The main idea is to take advantage of the <code>var()</code> fallback argument of a <em>second</em> registered variable, that is registered as non-inheriting. We set it to the fallback value on an ancestor. If <code>@property</code> is supported, then this property will not be defined on the element of interest, since it does not inherit. Any other properties referencing it will be <a href="https://drafts.csswg.org/css-variables/#invalid-at-computed-value-time">invalid at computed value time</a>, and thus any <code>var()</code> fallbacks will apply. If <code>@property</code> is <em>not</em> supported, the property will inherit as normal and thus using it becomes our fallback.</p>
<p>Here is an <a href="https://codepen.io/leaverou/pen/ExWqyzQ">example</a> with a simple green/red test to illustrate this concept:</p>
<pre><code>@property --test {
syntax: "*";
inherits: false;
}
html {
--test: red;
}
body {
background: var(--test, green);
}
</code></pre>
<iframe id="cp_embed_ExWqyzQ" src="https://codepen.io/anon/embed/ExWqyzQ?height=250&theme-id=1&slug-hash=ExWqyzQ&default-tab=result" height="250" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed ExWqyzQ" title="CodePen Embed ExWqyzQ" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>And here is how we can use the same concept to provide a fallback for the <code><select></code> example:</p>
<pre><code>@property --test {
syntax: "*";
inherits: false;
}
select {
--test: 1em; /* fallback */
--em: 1em;
}
optgroup:not([label]) {
font-size: var(--test, 0);
}
</code></pre>
<p>Here is the <a href="https://codepen.io/leaverou/pen/eYvqdYG">finished demo</a>.</p>
Custom properties with defaults: 3+1 strategies2021-10-15T00:00:00Zhttps://lea.verou.me/?p=3384<p>When developing customizable components, one often wants to expose various parameters of the styling as custom properties, and form a sort of <em>CSS API</em>. This is still underutlized, but there are libraries, e.g. <a href="https://shoelace.style/">Shoelace</a>, that already <a href="https://shoelace.style/components/switch?id=css-custom-properties">list</a> <a href="https://shoelace.style/components/progress-ring?id=css-custom-properties">custom</a> <a href="https://shoelace.style/components/image-comparer?id=css-custom-properties">properties</a> alongside other parts of each component’s API (even <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part">CSS parts</a>!).</p>
<p><em><strong>Note:</strong> I’m using “component” here broadly, as any reusable chunk of HTML/CSS/JS, not necessarily a web component or framework component. What we are going to discuss applies to reusable chunks of HTML just as much as it does to “proper” web components.</em></p>
<p>Let’s suppose we are designing a certain button styling, that looks like this:</p>
<p><img src="https://lea.verou.me/2021/10/custom-properties-with-defaults/images/outlined-button.gif" alt="" /></p>
<p>We want to support a <code>--color</code> custom property for creating color variations by setting multiple things internally:</p>
<pre><code>.fancy-button {
border: .1em solid var(--color);
background: transparent;
color: var(--color);
}
.fancy-button:hover {
background: var(--color);
color: white;
}
</code></pre>
<p>Note that with the code above, if no <code>--color</code> is set, the three declarations using it will be <a href="https://www.w3.org/TR/css-variables-1/#invalid-at-computed-value-time">IACVT</a> and thus we’ll get a nearly unstyled text-only button with no background on hover (<code>transparent</code>), no border on hover, and the default black text color (<code>canvastext</code> to be precise).</p>
<p><img src="https://lea.verou.me/2021/10/custom-properties-with-defaults/images/image.png" alt="" /></p>
<p>That’s no good! IT’s important that we set defaults. However, using the fallback parameter for this gets tedious, and <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">WET</a>:</p>
<pre><code class="language-css">.fancy-button {
border: .1em solid var(--color, black);
background: transparent;
color: var(--color, black);
}
.fancy-button:hover {
background: var(--color, black);
color: white;
}
</code></pre>
<p>To avoid the repetition and still ensure <code>--color</code> always has a value, many people do this:</p>
<pre><code>.fancy-button {
--color: black;
border: .1em solid var(--color);
background: transparent;
color: var(--color);
}
.fancy-button:hover {
background: var(--color);
color: white;
}
</code></pre>
<p>However, this is not ideal for a number of reasons:</p>
<ul>
<li>It means that people cannot take advantage of inheritance to set <code>--color</code> on an ancestor.</li>
<li>It means that people need to use specificity that overrides your own rules to set these properties. In this case this may only be <code>0,1,0</code>, but if your selectors are complex, it could end up being quite annoying (and introduce tight couplings, because developers should not need to know what your selectors are).</li>
</ul>
<p>If you insist going that route, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where"><code>:where()</code></a> can be a useful tool to reduce specificity of your selectors while having as fine grained selection criteria as you want. It’s also one of the features <a href="https://github.com/w3c/csswg-drafts/issues/1170">I proposed</a> for CSS, so I’m very proud that it’s now <a href="https://caniuse.com/mdn-css_selectors_where">supported everywhere</a>. <code>:where()</code> won’t solve the inheritance problem, but at least it will solve the specificity problem.</p>
<p>What if we still use the fallback parameter and use a variable for the fallback?</p>
<pre><code>.fancy-button {
--color-initial: black;
border: .1em solid var(--color, var(--color-initial));
background: transparent;
color: var(--color, var(--color-initial));
}
.fancy-button:hover {
background: var(--color, var(--color-initial));
color: white;
}
</code></pre>
<p>This works, and it has the advantage that people could even <em>customize your default</em> if they want to (though I cannot think of any use cases for that). But isn’t it so <em>horribly</em> verbose? What else could we do?</p>
<p>My preferred solution is what I call <em>pseudo-private custom properties</em>. You use a different property internally than the one you expose, which is set to the one you expose plus the fallback:</p>
<pre><code>.fancy-button {
--_color: var(--color, black);
border: .1em solid var(--_color);
background: transparent;
color: var(--_color);
}
.fancy-button:hover {
background: var(--_color);
color: white;
}
</code></pre>
<p>I tend to use the same name prepended with an underscore. Some people may flinch at the idea of private properties that aren’t really private, but I will remind you that we’ve done this in JS for over 20 years (we only got real <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields">private properties</a> fairly recently).</p>
<h2 id="bonus%3A-defaults-via-%40property-registration" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/10/custom-properties-with-defaults/#bonus%3A-defaults-via-%40property-registration">Bonus: Defaults via @property registration</a></h2>
<p>If <code>@property</code> is fair game (it’s <a href="https://caniuse.com/mdn-css_at-rules_property">only supported in Chromium</a>, but these days that still makes it supported in 70% of users’ browsers — which is a bit sad, but that’s another discussion), you could also set defaults that way:</p>
<pre><code>@property --color {
syntax: "<color>";
inherits: true;
initial-value: black;
}
.fancy-button {
border: .1em solid var(--color);
background: transparent;
color: var(--color);
}
.fancy-button:hover {
background: var(--color);
color: white;
}
</code></pre>
<p>Registering your property has several benefits (e.g. it makes it animatable), but if you’re only registering it for the purposes of setting a default, this way has several drawbacks:</p>
<ul>
<li>Property registration is global. Your component’s custom properties may clash with the host page’s custom properties, which is not great. The consequences of this can be quite dire, because <code>@property</code> fails silently, and the <a href="https://codepen.io/leaverou/pen/JjyYgow">last one wins</a> so you may just get the initial value of the host page’s property. In this case, that could very likely be <code>transparent</code>, with <a href="https://codepen.io/leaverou/pen/JjyYgow">terrible results</a>. And if your declaration is last and you get your own registered property, that means the rest of the page will also get yours, with equally potentially terrible results.</li>
<li>With this method you cannot set different initial values per declaration (although you usually don’t want to).</li>
<li>Not all custom property <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax">syntaxes</a> can be described via <code>@property</code> yet.</li>
</ul>
<h2 id="bonus%3A-customizable-single-checkbox-pure-css-switch" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/2021/10/custom-properties-with-defaults/#bonus%3A-customizable-single-checkbox-pure-css-switch">Bonus: Customizable single-checkbox pure CSS switch</a></h2>
<p>Just for the lulz, I made a switch (styling loosely inspired from <a href="https://shoelace.style/components/switch">Shoelace switch</a>) that is just a regular <code><input type=checkbox></code> with a pretty extensive custom property API:</p>
<iframe id="cp_embed_PoKPQYE" src="https://codepen.io/anon/embed/PoKPQYE?height=450&theme-id=1&slug-hash=PoKPQYE&default-tab=css,result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed PoKPQYE" title="CodePen Embed PoKPQYE" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>
<p>It is using the pseudo-private properties approach. Note how another bonus of this method is that there’s a little self-documentation right there about the component’s custom property API, even before any actual documentation is written.</p>
<p>As an aside, things like this switch make me wish it was possible to create web components that subclass existing elements. There is an existing — somewhat awkward — solution with <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/is">the <code>is</code> attribute</a>, but <a href="https://bugs.webkit.org/show_bug.cgi?id=182671">Apple is blocking it</a>. The alternative is to use a web component with <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals">ElementInternals</a> to make it form-associated and accessible and mirror all checkbox methods and properties, but that is way too heavyweight, and prone to breakage in the future, as native checkboxes add more methods. There is also a <a href="https://github.com/ungap/custom-elements#readme">polyfill</a>, but for a simple switch it may be a bit overkill. We really shouldn’t need to be painstakingly mirroring native elements to subclass them…</p>
<p><em>Enjoyed this article and want to learn more? I do teach courses on unlocking the full potential of CSS custom properties. You can watch my <a href="https://frontendmasters.com/workshops/css-variables/">Frontend Masters Dynamic CSS course (currently in production)</a>, or <a href="https://smashingconf.com/online-workshops/workshops/lea-verou-nov/">attend my upcoming Smashing workshop</a>.</em></p>
On Yak Shaving and <md-block>, a new HTML element for Markdown2021-11-26T00:00:00Zhttps://lea.verou.me/?p=3392<p>This week has been <a href="https://americanexpress.io/yak-shaving/">Yak Shaving</a> Galore. It went a bit like this:</p>
<ol>
<li>I’ve been working on a web component that I need for the project I’m working on. More on that later, but let’s call it <code><x-foo></code> for now.</li>
<li><em>Of course</em> that needs to be developed as a separate reusable library and released as a separate open source project. No, this is not the titular component, this was only level 1 of my multi-level yak shaving… 🤦🏽♀️</li>
<li>I wanted to showcase various usage examples of that component in its page, so I made another component for these demos: <code><x-foo-live></code>. This demo component would have markup with editable parts on one side and the live rendering on the other side.</li>
<li>I wanted the editable parts to autosize as you type. Hey, I’ve written a library for that in the past, it’s called <a href="https://github.com/leaverou/stretchy/">Stretchy</a>!</li>
<li>But Stretchy was not written in ESM, nor did it support Shadow DOM. I must rewrite Stretchy in ESM and support Shadow DOM first! Surely it won’t take more than a half hour, it’s a tiny library.</li>
<li><em>(It took more than a half hour)</em></li>
<li>Ok, now I have a nice lil’ module, but I also need to export IIFE as well, so that it’s compatible with Stretchy v1. Let’s switch to Rollup and npm scripts and ditch Gulp.</li>
<li>Oh look, Stretchy’s CSS is still written in Sass, even though it doesn’t really need it now. Let’s rewrite it to use CSS variables, use PostCSS for nesting, and use <code>conic-gradient()</code> instead of inline SVG data URIs.</li>
<li>Ok, Stretchy v2 is ready, now I need to update its docs. Oooh, it doesn’t have a README? I should add one. But I don’t want to duplicate content between the page and the README. Hmmm, if only…</li>
<li>I know! I’ll make a web component for rendering both inline and remote Markdown! I have an unfinished one lying around somewhere, surely it won’t take more than a couple hours to finish it?</li>
<li><em>(It took almost a day, two with docs, demos etc)</em></li>
<li><em>Done!</em> Here it is! <a href="https://md-block.verou.me/">https://md-block.verou.m</a><a href="https://md-block.verou.me/">e</a></li>
<li><em>Great!</em> Now I can update <a href="https://stretchy.verou.me/">Stretchy’s docs</a> and <a href="https://github.com/LeaVerou/stretchy/releases/tag/v2.0.0">release its v2</a></li>
<li><em>Great!</em> Now I can use Stretchy in my <code><x-foo-live></code> component demoing my <code><x-foo></code> component and be back to only one level of yak shaving!</li>
<li><em>Wow, it’s already Friday afternoon?!</em> 🤦🏽♀️😂</li>
</ol>
<p>Hopefully you find <a href="https://md-block.verou.me/"><md-block></md-block></a> useful! Enjoy!</p>
Releasing Color.js: A library that takes color seriously2022-06-29T00:00:00Zhttps://lea.verou.me/?p=3463<p><img src="https://lea.verou.me/2022/06/releasing-colorjs/images/image-2.png" alt="" /></p>
<p><em>Related: <a href="https://svgees.us/blog/colorjs-release.html">Chris’ blog post for the release of Color.js</a></em></p>
<p>This post has been long overdue: <a href="https://svgees.us/">Chris</a> and I started working on Color.js in 2020, over 2 years ago! It was shortly after I had finished the Color lecture <a href="https://designftw.mit.edu/">for the class I was teaching at MIT</a> and I was appalled by the lack of color libraries that did the things I needed for the demos in my slides. I asked Chris, “Hey, what if we make a Color library? You will bring your Color Science knowledge and I will bring my JS and API design knowledge. Wouldn’t this be the coolest color library ever?”. There was also a fair bit of discussion in the CSS WG about a native Color object for the Web Platform, and we needed to play around with JS for a while before we could work on an API that would be baked into browsers.</p>
<p>We had a prototype ready in a few months and presented it to the CSS WG. People loved it and some started using it despite it not being “officially” released. There was even a library that used Color.js as a dependency!</p>
<p>Once we got some experience from this usage, we worked on a draft specification for a Color API for the Web. In July 2021 <a href="https://github.com/w3c/css-houdini-drafts/issues/1047">we presented it again in a CSS WG Color breakout</a> and everyone agreed to <a href="https://github.com/wicg/color-api">incubate it in WICG, where it lives now</a>.</p>
<p><em><strong>Why can’t we just standardize the API in Color.js?</strong> While one is influenced by the other, a Web Platform API has different constraints and needs to follow more restricted design principles compared to a JS library, which can be more flexible. E.g. exotic properties (things like <code>color.lch.l</code>) are very common in JS libraries, but are <a href="https://github.com/w3ctag/design-principles/issues/16">now considered an antipattern in Web Platform APIs</a>.</em></p>
<p>Work on Color.js as well as the Color API continued, on and off as time permitted, but no release. There were always things to do and bugs to fix before more eyes would look at it. Because eyes <em>were</em> looking at it anyway, we even slapped a big fat warning on the homepage:</p>
<p><img src="https://lea.verou.me/2022/06/releasing-colorjs/images/image.png" alt="" /></p>
<p>Eventually a few days ago, I discovered that the <a href="https://www.npmjs.com/package/colorjs.io">Color.js package we had published on npm</a> somehow has over 6000 downloads per week, nearly all of them direct. I would not bat an eyelid at those numbers if we had released Color.js into the wild, but for a library we actively avoided mentioning to anyone outside of standards groups, it was rather odd.</p>
<p>How did this happen? Maybe it was the <a href="https://web.dev/shows/http-203/Uh95jZPTDfw/">HTTP 203 episode that mentioned it in passing</a>? Regardless, it gave us hope that it’s filling a very real need in the pretty crowded space of color manipulation libraries and it gave us a push to finally get it out there.</p>
<p>So here we are, releasing <a href="https://colorjs.io/">Color.js</a> into the wild. So what’s cool about it?</p>
<ul>
<li>Completely color space agnostic, each <code>Color</code> object just has a reference to a color space, a list of coordinates, and optionally an alpha.</li>
<li>Supports a large variety of color spaces including all color spaces from <a href="https://drafts.csswg.org/css-color-4/">CSS Color 4</a>, as well as the unofficial <a href="https://drafts.csswg.org/css-color-hdr">CSS Color HDR</a> draft.</li>
<li>Supports interpolation as defined in CSS Color 4</li>
<li>Doesn’t skimp on color science: does actual gamut mapping instead of naïve clipping, and actual chromatic adaptation when converting between color spaces with different white points.</li>
<li>Multiple DeltaE methods for calculating color difference (2000, CMC, 76, Jz, OK etc)</li>
<li>The library itself is written to be very modular and ESM-first (with CJS and IIFE bundles) and provides a tree-shakeable API as well.</li>
</ul>
<p>Enjoy: <a href="https://colorjs.io/">Color.js</a></p>
<p>There is also an entire (buggy, but usable) script in the website for realtime editable color demos that we call “Color Notebook”. It looks like this:</p>
<p><img src="https://lea.verou.me/2022/06/releasing-colorjs/images/image-3.png" alt="" /></p>
<p><img src="https://lea.verou.me/2022/06/releasing-colorjs/images/image-4.png" alt="" /></p>
<p>And you can <a href="https://colorjs.io/notebook/">create and share your own documents with live Color.js demos</a>. You log in with GitHub and the app saves in GitHub Gists.</p>
<p><img src="https://lea.verou.me/2022/06/releasing-colorjs/images/image-1.png" alt="" /></p>
<p>Color spaces presently supported by Color.js</p>
Introducing Rety: live coding, without the stress2022-07-13T00:00:00Zhttps://lea.verou.me/?p=3478<p>I recently spoke at CSS Day in Amsterdam. It was only my second f2f talk after the pandemic. It went down really well, both in person, and recently that the video was released:</p>
<p><a href="https://www.youtube.com/watch?v=ZuZizqDF4q8">https://www.youtube.com/watch?v=ZuZizqDF4q8</a></p>
<p>Here is a sample of tweets about it that made me particularly warm and fuzzy inside:
<a href="https://twitter.com/CSSDayConf/status/1542778793219301376">https://twitter.com/CSSDayConf/status/1542778793219301376</a></p>
<p><a href="https://twitter.com/jonpearse/status/1542490268322103296">https://twitter.com/jonpearse/status/1542490268322103296</a></p>
<p><a href="https://twitter.com/StuRobson/status/1542461048791384066">https://twitter.com/StuRobson/status/1542461048791384066</a></p>
<p><a href="https://twitter.com/vlh/status/1544483583544463361">https://twitter.com/vlh/status/1544483583544463361</a></p>
<p><a href="https://twitter.com/pawelgrzybek/status/1546861605824126980">https://twitter.com/pawelgrzybek/status/1546861605824126980</a></p>
<p><a href="https://twitter.com/parker%5C_codes/status/1547055116221497344">https://twitter.com/parker\_codes/status/1547055116221497344</a></p>
<p><a href="https://twitter.com/polarbirke/status/1547202631315214338">https://twitter.com/polarbirke/status/1547202631315214338</a></p>
<p><a href="https://twitter.com/unistyler/status/1544619175796252672">https://twitter.com/unistyler/status/1544619175796252672</a></p>
<p>There’s <a href="https://twitter.com/search?q=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DZuZizqDF4q8">a lot more where these came from too</a>.</p>
<p>This was not just my second post-pandemic talk, but my first talk using <em><a href="https://rety.verou.me/">Rety</a></em>, which is what this post is about.</p>
<p>As you may know, I love live coding as a teaching tool, and over the years <a href="https://twitter.com/aarongarciah/status/844212506365235200">it</a> <a href="https://twitter.com/gumnos/status/1118527972342935552">has become</a> <a href="https://twitter.com/ChrisFerdinandi/status/1027343408187277312">part</a> <a href="https://twitter.com/LinnOyenFarley/status/1011650208831287296">of</a> <a href="https://twitter.com/feross/status/928018779115724800">my</a> <a href="https://twitter.com/HenriHelvetica/status/1011630698984361985">trademark</a> <a href="https://twitter.com/johnallsopp/status/926417129070456832">speaking</a> <a href="https://bradfrost.com/blog/post/on-speaking/#:~:text=Don%E2%80%99t%20live%20code%20%E2%80%93%20This%20applies%20to%20everyone%20except%20Lea%20Verou%2C%20who%20is%20an%20absolute%20beast">style</a>.</p>
<p>When combined with some kind of interactive preview, it allows the speaker to demonstrate not only the final state of a coding snippet, but also how you get there, and what the intermediate results are. <strong>Live coding is to programming what a blackboard is to math or physics.</strong></p>
<p>But it does create a unique challenge: My live coded slides don’t make sense without me. This may be acceptable for a conference talk, which is usually recorded, but not in other contexts, such as teaching a university course, where all instructors need to be able to teach all lectures, and students need to be able to quickly refer to examples shown.</p>
<p>Back in the fall of 2021, when we were preparing for the second iteration of our course, <a href="https://designftw.mit.edu/">Design for the Web: Languages and User Interfaces</a>, this came up as a pressing issue. The current state of the course <em>required</em> me to be there to teach my lectures, and this may well be the last year I teach it, since I’m finishing up my PhD soon.</p>
<p>I didn’t want to completely remove live coding from my slides, as I truly believe it is the perfect implementation of the <em>“show, don’t tell”</em> teaching adage for certain things, so I thought instead: what if I could <em>record</em> my live coding, and make it replayable?</p>
<p>Doing so manually seemed like cruel and unusual punishment. And thus, <strong><em><a href="https://rety.verou.me/">Rety</a></em></strong> was born (pronounced like the “rety” in “retype”).</p>
<p>While originally the plan was for me to still live code, and have the <em><a href="https://rety.verou.me/">Rety</a></em> functionality there for students and future instructors, I ended up using it during my own lectures as well, as I concluded that a well crafted <a href="https://rety.verou.me/#rety-actions">Rety script</a> was strictly superior to me doing the live coding:</p>
<ul>
<li>Same progressive development as a live demo</li>
<li>It still affords unplanned demonstrations (e.g. to answer a question), since Rety still works with the same editors, and I could always pause it and take over if needed.</li>
<li>I could record myself and edit the script to maximize education value and minimize typos, delays, fumbling etc.</li>
<li>People can consume typed text far faster than people can type text. This is why most video tutorials speed up the typing. With Rety, typing speed is adjustable, and doesn’t need to match mine.</li>
</ul>
<p>After test driving it for our course the entire spring 2022 semester, it went through the ultimate test in June 2022: I used it for my <a href="https://cssday.nl/2022">CSSDay</a> conference talk. You can <a href="https://www.youtube.com/watch?v=ZuZizqDF4q8">watch the talk here</a> (first live demo at 7:15).</p>
<p>Right now <em><a href="https://rety.verou.me/">Rety</a></em> is just a set of two classes: <code>Recorder</code> and <code>Replayer</code>, which are used entirely independently. The exact UI is left up to the Rety user. E.g. to use it in my slides, I integrated it with the Live Demo plugin of <a href="https://inspirejs.org/">Inspire.js</a> (it is automatically included if a <code><script class="demo-script" type="application/json"></code> is found in a live demo slide).</p>
<p>The library could use more docs and <em>some</em> tests and I have doubts about the API, but I figured I should release it it earlier rather than later (it’s already been sitting in a repo for 7 months). After all, what best time to release it than when the first Rety talk is <a href="https://frontendfoc.us/issues/549">still making the rounds</a>?</p>
<p>My vision is to ultimately evolve and standardize the <a href="https://rety.verou.me/#actions">Rety script</a> format, so that it can be used to describe a coding interaction across a variety of tools. There are so many possibilities!</p>
<ul>
<li>Wouldn’t it be cool if CodePen and similar playgrounds supported embedding a <em>Rety</em> script into a pen?</li>
<li>What if you could store <em>Rety</em> scripts in a repo and editors like VS Code recognized them and let you replay them?</li>
</ul>
<p>Enjoy: <a href="https://rety.verou.me/">Rety</a></p>
What is the best way to mark up an exclusive button group?2022-07-14T00:00:00Zhttps://lea.verou.me/?p=3484<p>A few days ago I asked Twitter a seemingly simple question <em>(I meant <code>aria-pressed</code>, not <code>aria-selected</code> but Twitter doesn’t allow edits…)</em>:</p>
<p><a href="https://twitter.com/LeaVerou/status/1545712667515654144">https://twitter.com/LeaVerou/status/1545712667515654144</a></p>
<p>For background, I was implementing a web component for an app I’m working on at work and I was getting into some pretty weird rabbit holes with my approach of generating radios and labels.</p>
<p>Unsurprisingly, <a href="https://twitter.com/heydonworks/status/1545713406187114496">most</a> <a href="https://twitter.com/WebReflection/status/1545722200598233097">people</a> <a href="https://twitter.com/gumnos/status/1545745858339250176">thought</a> <a href="https://twitter.com/TimBrock_DtD/status/1545714402367885313">the</a> <a href="https://twitter.com/tolgamndl/status/1545714439000825856">best</a> <a href="https://twitter.com/jorgepinon/status/1545743881169510401">solution</a> <a href="https://twitter.com/dotstepan/status/1545729640731447298">is</a> <a href="https://twitter.com/sunlix/status/1545747700427145216">radio</a> <a href="https://twitter.com/gazjoy/status/1545754675395297280">buttons</a> <a href="https://twitter.com/devongovett/status/1547593573448552450">and</a> <a href="https://twitter.com/rmors_/status/1547568003520221186">labels</a>. After all, it works without CSS, right? Progressive enhancement and everything?</p>
<p>That’s what I thought too. I had contorted my component to generate labels and radios in the Shadow DOM from buttons in the light DOM, which resulted in awkward code and awkward CSS, but I felt I was fighting the good fight and doing the best thing for accessibility.</p>
<p>All this was challenged when the actual accessibility expert, <a href="https://twitter.com/LeonieWatson">Léonie Watson</a> chimed in. For those of you who don’t know her, <a href="https://tink.uk/about-leonie/">she is pretty much <em>the</em> expert</a> when it comes to web accessibility and standards. She is also visually impaired herself, giving her a firsthand experience many other a11y aficionados lack. Her recommendation was contrary to what most others were saying:
<a href="https://twitter.com/LeonieWatson/status/1545745436740313089">https://twitter.com/LeonieWatson/status/1545745436740313089</a></p>
<p>She went on to make the point that if a design <em>looks</em> like buttons, it should <em>act</em> like buttons, otherwise there are mismatched expectations and poor UX for AT users:</p>
<p><a href="https://twitter.com/LeonieWatson/status/1545762058339319808">https://twitter.com/LeonieWatson/status/1545762058339319808</a></p>
<p><a href="https://twitter.com/LeonieWatson/status/1545757022645325824">https://twitter.com/LeonieWatson/status/1545757022645325824</a></p>
<p>In case you were wondering if state would be equally noticeable with <code>aria-pressed</code> and buttons, it is:</p>
<p><a href="https://twitter.com/LeonieWatson/status/1545763493412052992">https://twitter.com/LeonieWatson/status/1545763493412052992</a></p>
<p>And some advice on grouping:</p>
<p><a href="https://twitter.com/LeonieWatson/status/1545745923011117057">https://twitter.com/LeonieWatson/status/1545745923011117057</a></p>
<p>In theory doing this in Shadow DOM and/or using ElementInternals implicit roles should be fine, <a href="https://twitter.com/LeonieWatson/status/1547544701036888065">though in practice we’ve had some trouble with that</a>.</p>
<p>Today I posted <a href="https://projects.verou.me/nudeforms/button-group/">my attempt to implement what we’ve discussed in a <code><button-group></code> component</a>, which restarted the discussion.</p>
<p><img src="https://lea.verou.me/2022/07/button-group/images/image.png" alt="" /></p>
<p>Its <a href="https://github.com/LeaVerou/nudeforms/tree/main/button-group">implementation is right here</a> if you want to improve it further! And make sure to <a href="https://twitter.com/LeaVerou/status/1545712667515654144">check out the actual Twitter thread</a>, as there is a lot of good stuff I couldn’t include in this!</p>
<p><em><strong>Edit:</strong> Léonie wrote a blog post too, <a href="https://tink.uk/perceived-affordances-and-the-functionality-mismatch/">Perceived affordances and the functionality mismatch</a></em>. <em>It’s a great read.</em></p>
Help design the State of CSS Survey 2022!2022-07-28T00:00:00Zhttps://lea.verou.me/?p=3493<p>Since 2019, the annual <a href="https://stateofcss.com/en-us/">State of CSS survey</a> has collected feedback from web developers from across the world to try and take the pulse of the CSS ecosystem, and it’s become a valuable resource not only for CSS developers, but also for browser vendors. This summer, one of my side projects is helping out with survey design and outreach for the <a href="https://stateofcss.com/en-us/">State of CSS survey</a>, thanks to a generous Google <a href="https://web.dev/ui-fund/">UI fund</a> grant.</p>
<p>The target is for the survey to launch in mid September, and we are currently working on the outline. So far we have created a preliminary outline based on last year’s survey and early research. All our work happens is in the open, in <a href="https://github.com/Devographics/Surveys">this repo</a>. Here are some of the <a href="https://github.com/Devographics/Surveys/issues/1">changes from last year’s survey</a>:</p>
<ul>
<li>Removed the Pre-processors category as it feels like there isn’t too much debate around that area.</li>
<li>Got rid of “which browser do you primarily develop in?” question as we already ask which browsers people test in.</li>
<li>Merged “Opinions” and “Environments” sections into new “Usage” section.</li>
<li>Moved browsers question to “Other Tools”.</li>
<li>New features:
<ul>
<li>currentcolor</li>
<li><code>color-mix()</code></li>
<li>Wide gamut colors</li>
<li><code>scroll-behavior</code></li>
<li><code>scroll-padding</code></li>
<li><code>font-palette</code></li>
<li><code>:focus-visible</code></li>
<li><code>:has()</code> pseudo-class</li>
<li><code>:where()</code> pseudo-class</li>
<li>Cascade Layers</li>
<li>Houdini Paint API</li>
<li>and there are <a href="https://github.com/Devographics/Surveys/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc">several others we are considering</a></li>
</ul>
</li>
</ul>
<p>We are currently looking for feedback from the community, including suggesting CSS features to ask about, libraries and tools, or even new questions altogether.</p>
<p>There are also <a href="https://github.com/Devographics/Monorepo/issues/99">some design issues</a> to flesh out, you’re welcome to weigh in there too.</p>
<p>If you want to quickly vote on which features are most important for you to make it into the survey, you can do that either via GitHub 👍🏼reactions, or <a href="https://projects.verou.me/mavoice/?repo=devographics/surveys&labels=State%20of%20CSS%202022">here</a> (which uses GitHub reactions behind the scenes). Do note that reactions are only one metric among many we will use to consider items.</p>
<p>The feedback period will be open until <strong>August 20</strong>, then we will start working on launching the survey.</p>
<p>Do note that <strong>browser makers are looking at this</strong> and similar surveys to prioritize what to implement. This is why Google is sponsoring this project. So any effort you put into survey outline feedback, and on responding to the survey when it’s ready, could come back to you tenfold when your favorite CSS features get implemented faster!</p>
On ratings and meters2022-08-12T00:00:00Zhttps://lea.verou.me/?p=3502<p>I always thought that the semantically appropriate way to represent a rating (e.g. a star rating) is a <code><meter></code> element. They essentially convey the same type of information, the star rating is just a different presentation.</p>
<p><img src="https://lea.verou.me/2022/08/on-ratings-and-meters/images/image.png" alt="" /></p>
<p>An example of a star rating widget, from Amazon</p>
<p>However, trying to style a <code><meter></code> element to look like a star rating is <a href="https://codepen.io/leaverou/pen/WNErYON">…tricky at best</a>. Not to mention that this approach won’t even work in Shadow trees (unless you include the CSS in every single shadow tree).</p>
<p>So, I set out to create a proper web component for star ratings. The first conundrum was, how does this relate to a <code><meter></code> element?</p>
<ul>
<li>Option 1: Should it extend <code><meter></code> <a href="https://webreflection.medium.com/extending-built-in-elements-9dce404b75b4">using builtin extends</a>?</li>
<li>Option 2: Should it use a web component with a <code><meter></code> in Shadow DOM?</li>
<li>Option 3: Should it be an entirely separate web component that just uses a <code>meter</code> ARIA Role and related ARIA attributes?</li>
</ul>
<p>This is what the code would look like:</p>
<pre><code class="language-html"><!-- Option 1 -->
<meter is="meter-discrete" max="5" value="3.5"></meter>
<!-- Options 2 & 3 -->
<meter-discrete max="5" value="3.5"></meter-discrete>
</code></pre>
<p><a href="https://bugs.webkit.org/show_bug.cgi?id=182671">Safari has all but killed built-in extends</a>, but there is <a href="https://github.com/ungap/custom-elements#readme">a very small polyfill</a>, so I didn’t mind too much. I first decided to go with that, but it turns out <a href="https://codepen.io/leaverou/pen/gOedNYv?editors=1111">you can’t even mess with the Shadow DOM</a> of the element you’re extending. You have no access to the existing Shadow DOM of the element, because it’s closed, and you cannot attach a new one. So there’s no way to add encapsulated styles, which was a strong requirement of my use case.</p>
<p>I did some work on Option 2, but I quickly discovered that having an internal <code><meter></code> that everything goes through was not worth it, and it was far easier to implement it myself, with appropriate implicit ARIA through <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals">ElementInternals</a>.</p>
<p>The next dilemma was even more of a conundrum: A <code><meter></code> is not editable by default, but for a rating widget, you need it to be editable at least sometimes (e.g. see <a href="https://shoelace.style/components/rating">Shoelace Rating</a> for an example). <strong>There is no established convention in HTML for elements that are readonly by default, and editable only some of the time.</strong> All editable elements we have are basically form controls that can <em>lose</em> editability through the <code>readonly</code> attribute. For anything else, I suppose there is <code>contentEditable</code> but there is no way for web components to hook into it and expose custom editing UI that overrides the one generated by the browser.</p>
<p>In the end what I ended up doing was creating two components:</p>
<ul>
<li>A <code><meter-discrete></code> component that is a discrete version of <code><meter></code></li>
<li>An <code><nd-rating></code> component that inherits from <code><meter-discrete></code> but is editable (unless <code>readonly</code> is specified)</li>
</ul>
<p>I’m still unsure if this is the right way. There were a couple issues with it.</p>
<p>The first problem was related to <strong>encapsulation</strong>. I like to use a private <code>#internals</code> property for an element’s <code>ElementInternals</code> instance. However, <code><nd-rating></code> needed to modify the internals of its parent, <a href="https://css-tricks.com/creating-custom-form-controls-with-elementinternals/">to add form association stuff</a>, so I could not use a private property anymore, and you cannot attach a separate <code>ElementInternals</code> object. I ended up <a href="https://github.com/LeaVerou/nudeui/blob/main/meter-discrete/meter-discrete.js#L1">going for a <code>Symbol</code> property that the parent exports</a>, but it still doesn’t feel like a great solution as it breaks encapsulation. Ideally JS needs <a href="https://en.wikipedia.org/wiki/Access_modifiers">protected class fields</a>, but <a href="https://github.com/tc39/proposal-class-fields/issues/86">it doesn’t look like that’s happening anytime soon</a>.</p>
<p>The other problem was related to <strong>semantics</strong>. Is it still semantically a <code><meter></code> when it’s editable, or does it then become closer to a slider that you set by hovering instead of dragging? I decided to ignore that thought for now, but it does make me a little uneasy.</p>
<p>Anyhow, you can find my experiments at <a href="https://nudeui.com/">nudeui.com</a>:</p>
<ul>
<li><a href="https://nudeui.com/meter-discrete/"><code><meter-discrete></code></a></li>
<li><a href="https://nudeui.com/nd-rating/"><code><nd-rating></code></a></li>
</ul>
<p><strong>All NudeUI components are very much works in progress</strong> and mainly my personal experiments, but if you feel like it, please report issues in <a href="https://github.com/LeaVerou/nudeui/tree/main/">the repo</a>. I can’t promise I’ll get to them though!</p>
State of CSS 2022 now open!2022-10-03T00:00:00Zhttps://lea.verou.me/?p=3511<p><a href="https://survey.devographics.com/survey/state-of-css/2022?source=leaverou" class="call-to-action">Take State of CSS 2022 survey</a></p>
<p>A while ago I posted a <a href="https://lea.verou.me/2022/07/help-design-the-state-of-css-survey-2022/">call for feedback to inform the design of the State of CSS 2022 survey</a>. The response has been overwhelming and it was glorious. We got quite a lot of <a href="https://github.com/Devographics/surveys/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc">proposals</a>, <a href="https://github.com/Devographics/surveys/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc">feedback</a>, <a href="https://projects.verou.me/mavoice/?repo=devographics/surveys&labels=State%20of%20CSS%202022">votes</a>. But that also meant we had to make some tough decisions about what gets in the survey and what doesn’t, otherwise we’d end up with a survey so long nobody would want to finish it!</p>
<p>In the end we <a href="https://github.com/orgs/Devographics/projects/1/views/1">added questions about 15 new CSS features based on proposals in that repo, and decided against adding 9</a>. Overall, there are 30 new CSS features the 2022 survey asks about. To make space for all of that, we also <a href="https://github.com/Devographics/surveys/issues/37">removed a few</a> that were not really shining much light into what developers do anymore, and also <a href="https://github.com/Devographics/surveys/issues/11">a couple others that were not actually about CSS</a>.</p>
<p>However, CSS features are not the only — or even the most important questions being asked.</p>
<p>Last year, some of the freeform questions about pain points were particularly useful to browser vendors for prioritizing implementation and standards work, and we expect this to be true this year as well. We put considerable effort into <a href="https://github.com/Devographics/surveys/issues/36">redesigning these freeform questions to make them more intuitive</a>, while maintaining their helpfulness for browser vendors:</p>
<p><img src="https://lea.verou.me/2022/10/state-of-css-2022-now-open/images/image-3.png" alt="" /></p>
<p>We hope the new wording makes it more clear that these are mutually exclusive, so that respondents do not feel they need to duplicate their answers.</p>
<p>One of the new questions I’m excited about is this question to gauge whether the respondent spends more time writing JS or CSS:</p>
<p><img src="https://lea.verou.me/2022/10/state-of-css-2022-now-open/images/image-1.png" alt="" /></p>
<p>A focus of this year’s State of CSS survey is to reach a broader range of developers; a majority of respondents of past surveys has been JS developers who also write CSS, rather than developers that focus on CSS equally or even primarily. This is a natural consequence of this having been spun off the <a href="https://stateofjs.com/en-us/">State of JS survey</a>. To truly see what the <em>State of CSS</em> is in 2022, we need input from all types of developers, as developers with different focus have different needs and priorities. This question will allow us to evaluate how well we have reached this goal, and going forward, whether we are improving every year.</p>
<p>Another thing I’m excited about in this year’s survey is the ability to add freeform comments to <em>any</em> question.</p>
<figure>
<p><img src="https://lea.verou.me/2022/10/state-of-css-2022-now-open/images/comments.gif" alt="" /></p>
<figcaption>Adding freeform comments to a question</figcaption>
</figure>
<p>It’s often hard to tell what the background is behind each of the three answers: are people not using a given feature due to poor browser support, poor ergonomics, or some other reason? When people <em>do</em> use a feature, was their experience good or bad? Would they use it again?</p>
<p>We <a href="https://github.com/Devographics/surveys/issues/41">went back and forth</a> <a href="https://github.com/Devographics/Monorepo/issues/99">many times</a> about having a more structured followup question there, but in the end settled on a simple freeform field for this first iteration. Maybe next year it will be more structured, depending on how people use it this year.</p>
<p>So, without further ado, the survey is finally open for responses:</p>
<p><a href="https://survey.devographics.com/survey/state-of-css/2022?source=leaverou">Take State of CSS 2022 survey</a></p>
<p>This survey is not just for fun: <strong>the results actually inform what browsers prioritize for implementation</strong>. So by spending a few minutes on a thoughtful and comprehensive response, you can actually make both your and other developers’ lives better! What are you waiting for?</p>
Position Statement for the 2022 W3C TAG Election2022-11-07T00:00:00Zhttps://lea.verou.me/?p=3531<p><em><strong>Update:</strong> <a href="https://www.w3.org/blog/news/archives/9787">I got re-elected</a>!! Thank you for trusting me once more with this huge responsibility towards the Open Web. I will continue to do my best to justify the confidence the W3C membership has placed in me.</em> 🥹</p>
<p><em><strong>Context:</strong> In <a href="https://lea.verou.me/2020/11/tag/">2020</a>, I ran for the TAG election for the first time and had the great honor of being elected by the W3C membership. This year, I’m running for re-election. The W3C Technical Architecture Group (TAG) is the Working Group that ensures that Web Platform technologies are usable and follow consistent design principles, whether they are created inside or outside W3C. It advocates for the needs of everyone who uses the Web and everyone who works on the Web. If you work for a company that is a <a href="https://www.w3.org/Consortium/Member/List">W3C Member</a>, please consider encouraging your AC rep to vote for me! My candidate statement follows.</em></p>
<p>I’m <a href="https://en.wikipedia.org/wiki/Lea_Verou">Lea</a>, and I’m running for re-election to the TAG to continue applying my usability research, CSS WG, and TAG experience to help W3C stay connected to the developer community, and to better serve their needs by ensuring web platform features are not only powerful, but also learnable and approachable, with a smooth ease-of-use to complexity curve.</p>
<p>I wear many hats. My background spans almost two decades of web design & development experience, one decade of standards work in the CSS WG, nearly a decade of PhD level human-computer interaction research & teaching at <a href="https://mit.edu/">MIT</a>, and over a decade of educating web developers through <a href="https://lea.verou.me/speaking/">talks</a>, <a href="https://lea.verou.me/publications/">books, articles</a>, and helping them through my <a href="https://lea.verou.me/projects/">dozens of open source projects</a>, some of which are used on <a href="https://www.npmjs.com/package/prismjs">millions of websites</a>. For those unfamiliar with my background, I encourage taking a look at <a href="https://lea.verou.me/2020/11/tag/">my 2020 candidate statement</a>.</p>
<p>In 2020, I had the great honor of being elected to serve on the TAG by the W3C membership. In the two years I have served on the TAG, I participated in over 70 design reviews and helped prioritize API design in our reviewing. I have been <a href="https://twitter.com/yoavweiss/status/1430807031364075520">publicly praised</a> for the quality of design reviews I led.</p>
<p>It is important that the TAG does not operate in a vacuum: The primary purpose of our work is to serve developers and end-users by ensuring web platform features are usable, secure and privacy preserving. I have used my experience during design reviews to make sure we remain connected to this mission.</p>
<p>Together with Sangwhan Moon, I took the lead on our <a href="https://w3ctag.github.io/design-principles/">Web Platform Design Principles</a> effort, which documents the principles that underlie Web Platform features — previously only existing in WG lore. The Web Platform is going through an explosion of new features; only in the last year the TAG received almost a hundred design review requests. With this volume, it is important that reviews are consistent, transparent, and fast. Evolving our published design principles helps with all three goals.</p>
<p>The Web ecosystem is not just the Web Platform itself, but also the various tools and libraries out there. I <a href="https://github.com/w3ctag/design-principles/issues/268">started a project</a> to publish a subset of the design principles that apply to web developers, to help them in creating Web Platform compatible APIs. After all, with web components, web developers are now HTML designers, with Houdini APIs, they are now CSS designers, and with JS, they’ve been JS API designers since forever. The project is currently in its infancy, and If elected, it will be one of my tasks to get it published within my next term.</p>
<p>As a Greek woman, I bring both a Mediterranean and European perspective that diversifies the TAG and as a fully bilingual Greek and English speaker, I can fully participate in rapid technical discussions while also having an appreciation of the Internationalization needs of those who use the Web in languages other than English.</p>
<p>To ensure my participation has been beneficial for the TAG, I reached out to the chairs for feedback before deciding to run again. Both were very positive and strongly encouraged me to run again.</p>
<p>As someone not employed at a big tech company, I am not influenced by any particular company line. My only agenda is to lead the Web Platform to its full potential, and if re-elected, I’m willing to commit to spending the requisite hundreds of hours working towards that goal over the next two years. This was just the beginning, there is so much more important work to be done!</p>
<p><em>I would like to thank Open JS Foundation for graciously funding my TAG-related travel, in the event that I am re-elected, and both OpenJS Foundation and Bocoup for funding it during my first term.</em></p>
Contrast Ratio has a new home — and this is great news!2023-03-18T00:00:00Zhttps://lea.verou.me/?p=3592<p>It has been over a decade when I <a href="https://lea.verou.me/2012/10/easy-color-contrast-ratios/">launched</a> <a href="https://contrast-ratio.com/">contrast-ratio.com</a>, an app to calculate the WCAG 2.1 contrast ratio between any two CSS colors. At the time, all similar tools suffered from several flaws when being used for CSS editing:</p>
<ul>
<li>
<p>No support for semi-transparent colors (Since WCAG included no guidance for alpha transparency — I had to do <a href="https://lists.w3.org/Archives/Public/w3c-wai-ig/2012OctDec/0011.html">original research</a> to calculate the contrast ratio range for that case)</p>
</li>
<li>
<p>No support for color formats other than hex or (at best) RGB with sliders. I wanted something where I could just paste a CSS color just like I had it specified in my code (e.g. <code>hsl(220 10% 90%</code>), possibly tweak it a bit to pass, then paste it back. I didn’t want to use unintuitive hex colors, and I didn’t want to fiddle with sliders.</p>
</li>
<li>
<p>Poor UX, often calculating the actual ratio required further user actions, making iteration tedious</p>
</li>
</ul>
<p>Over the years, <a href="https://contrast-ratio.com/">contrast-ratio.com</a> grew in popularity: it was recommended in <a href="https://www.google.com/search?q=%22contrast-ratio.com%22+OR+%22leaverou.github.io%2Fcontrast-ratio%22&tbm=bks">several books</a>, talks, and workshops. It basically became the standard URL developers would visit for this purpose.</p>
<p>However, I’ve been too busy to work on it further beyond just merging pull requests. My time is currently split between the <a href="https://github.com/leaverou/">dozens of open source projects</a> I have started and maintain, <a href="https://lea.verou.me/2022/11/tag-2/">my TAG work</a>, <a href="https://github.com/w3c/csswg-drafts/issues?q=is%3Aopen+involves%3Aleaverou+sort%3Aupdated-desc+">my CSS WG work</a>, and my <a href="https://designftw.mit.edu/">teaching</a> & <a href="https://lea.verou.me/2014/02/im-going-to-mit/">research</a> at MIT.</p>
<p>Therefore, when <a href="https://twitter.com/rosshudgens/">Ross</a> and <a href="https://twitter.com/drewpager">Drew</a> from <a href="https://www.siegemedia.com/">Siege Media</a> approached me with a generous offer to buy the domain, and a commitment to take over maintainship of the <a href="https://github.com/siege-media/contrast-ratio/">open source project</a>, I was cautiously optimistic. But now, after having seen some of their plans for it, I could not be more certain that the future of this tool is much brighter with them.</p>
<p>Please join me in welcoming them to the project and help them get settled in as new stewards!</p>
<p>ETA: <a href="https://www.prnewswire.com/news-releases/contrast-ratio-is-now-part-of-siege-media-301790441.html">Siege Media Press Release</a></p>
JS private class fields considered harmful2023-04-28T00:00:00Zhttps://lea.verou.me/?p=3599<p>Today I mourn. What am I mourning? Encapsulation. At least in my projects.</p>
<p><strong>As a library author, I’ve decided to avoid private class fields from now on and gradually refactor them out of my existing libraries</strong>.</p>
<p>Why did I make such a drastic decision?</p>
<p>It all started a few days ago, when I was building a <a href="https://vuejs.org/">Vue 3</a> app that used <a href="https://colorjs.io/">Color.js</a> Color objects. For context, Vue 3 uses proxies to implement its reactivity system, just like <a href="https://mavo.io/">Mavo</a> did back in 2016 (the first one to do so as far as I’m aware). I was getting several errors and upon tracking them down I had a very sad realization: <strong>instances of classes that use private fields cannot be proxied</strong><em>.</em></p>
<p>I will let that sink in for a bit. <strong>Private fields, proxies, pick one, you can’t have both.</strong> Here is a <a href="https://codepen.io/leaverou/pen/ExdWwax?editors=0012">reduced testcase</a> illustrating the problem.</p>
<p><img src="https://lea.verou.me/2023/04/private-fields-considered-harmful/images/image.png" alt="" /></p>
<p>Basically, because a Proxy creates a <em>different</em> object, it breaks both strict equality (<code>obj1 === obj2</code>), as well as private properties. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#no_private_property_forwarding">MDN even has a whole section on this</a>. Unfortunately, the workaround proposed is no help when proxies are used to implement reactivity, so when <a href="https://github.com/vuejs/core/issues/8149">I tried to report this as a Vue bug</a>, it was (rightly) closed as wontfix. It would not be possible to fix this in <a href="https://mavo.io/">Mavo</a> either, for the same reason.</p>
<p><a href="https://twitter.com/LeaVerou/status/1639043004190031876">I joined TC39</a> fairly recently, so I was not aware of the background when proxies or private class fields were designed. Several fellow TC39 members filled me in on the discussions from back then. A lot of the background is in <a href="https://github.com/tc39/proposal-class-fields/issues/106">this super long thread</a>, some interesting tl;drs as replies to my tweet:</p>
<p><a href="https://twitter.com/LeaVerou/status/1650562320702099457">https://twitter.com/LeaVerou/status/1650562320702099457</a></p>
<p>After a lot of back and forth, I decided <strong>I cannot justify using private properties going forwards</strong>. The tradeoff is simply not worth it. There is no real workaround for proxy-ability, whereas for private fields there is always private-by-convention. Does it suck? Absolutely. However, <strong>a sucky workaround is better than a nonexistent workaround</strong>.</p>
<p><strong>Also, I control the internal implementation of my classes, whereas proxying happens by other parties.</strong> As a library user, it must be incredibly confusing to have to deal with errors about access to private fields in a class you did not write.</p>
<p>This was one of the saddest PRs I have ever written <a href="https://github.com/LeaVerou/color.js/pull/306">https://github.com/LeaVerou/color.js/pull/306</a>. It feels like such a huge step backwards. I’ve waited years for private fields to be supported everywhere and relished when they got there. I was among the first library authors to adopt them in library code, before a lot of tooling even parsed them properly (and <a href="https://github.com/LeaVerou/color.js/issues/220">some still don’t</a>).</p>
<p>Sure, they were kind of annoying to use (you usually want protected, i.e. visible to subclasses, not actually private), but they were better than nothing. I was not joking in the first paragraph; I am literally grieving.</p>
<p>I may still use private fields on a case by case basis, where I cannot imagine objects being proxied being very useful, for example in web components. But from now on I will not reach to them without thought, like I have been for the past couple of years.</p>
Migrating Disqus from WP to 11ty2023-07-18T00:00:00Zhttps://lea.verou.me/blog/2023/preserve-disqus/<p>So I recently <a href="https://lea.verou.me/blog/2023/going-lean/">ported my 14 year old blog from WordPress to Eleventy</a>.</p>
<p>I had been using <a href="https://disqus.com/">Disqus</a> for comments for years, so I didn’t want to lose them, even if I ended up using a different solution for the future (or no comments at all).</p>
<p>Looking around for an existing solution did not yield many results.
There’s Zach’s <a href="https://github.com/11ty/eleventy-import-disqus">eleventy-import-disqus</a> but it’s aimed at importing Disqus comments as static copies,
but I wanted to have the option to continue using Disqus.</p>
<p>Looking at the WP generated HTML source, I noticed that Disqus was using the WP post id (a number that is not displayed in the UI) to link its threads to the posts.
However, the importer I used did not preserve the post ids as metadata (filed issue <a href="https://github.com/lonekorean/wordpress-export-to-markdown/issues/95">#95</a>).
What to do?</p>
<h2 id="getting-the-wp-post-id" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/preserve-disqus/#getting-the-wp-post-id">Getting the WP post id</a></h2>
<p>My first thought was to add the post id to each post manually, but use a <code>HEAD</code> request to my existing blog to read it from the <code>Link</code> header, possibly en masse.
My second thought was, if I can use JS to get it, maybe I can include Disqus dynamically, through JS, after it procures this number.
Then I remembered that 11ty can handle any number of different data sources, and combines them all into a single data object.
If I could build an index of slug → post id as another data file, I could add a post id via JS in the 11ty config.</p>
<p>My last epiphany was realizing I didn’t need any HTTP requests to get the post id: it was all in the exported sitemap XML, just unused by the importer!
Indeed, each <code><item></code> included a <code><wp:post_id></code> element with the post id and a <code><link></code> element with the URL.
I tried to open it in Chrome so I could run some JS on it and build the index, but it complained of parse errors.
When I fixed them, the tab crashed under its sheer size.</p>
<p>I needed to remove non-relevant data, and I needed to do it fast.
All I really needed was the post id, the slug, and the containing <code><item></code> element.
Since this did not just contain posts, but also other types of content, such as attachments or custom blocks,
we also needed to retain <code><wp:post_type></code> so we can filter these out.
I copied the XML over to a separate file, and run a series of find & replaces in VS Code:</p>
<ol>
<li><code>^(?!.*(wp:post_id|wp:post_type|</?item>|</?link>)).+\n</code> (regex) with empty string</li>
<li><code>\n{3,}</code> (regex) with <code>\n</code> (to remove empty lines)</li>
<li><code>wp:post</code> with <code>post</code> to remove namespaces and make the XML easier to handle</li>
<li><code>https://lea.verou.me/</code> with empty string and <code></link></code> with <code></link></code> to keep just the <code>yyyy/mm/slug</code> part of the URL</li>
</ol>
<p>Then added <code><?xml version="1.0" encoding="UTF-8" ?></code> at the top and wrapped everything in a <code><root></code> element to make it valid XML.</p>
<p>This resulted in a series of <code><item></code> elements that looked like this:</p>
<pre><code class="language-xml"><item>
<link>2023/04/private-fields-considered-harmful</link>
<post_id>3599</post_id>
<post_type><![CDATA[post]]></post_type>
</item>
<item>
<link>2023/04/private-fields-considered-harmful/image-27</link>
<post_id>3600</post_id>
<post_type><![CDATA[attachment]]></post_type>
</item>
</code></pre>
<p>At this point, we have exuahsted the capabilities of find & replace; it’s time for some JS!</p>
<p>I opened the file in Chrome and ran:</p>
<pre><code class="language-js">copy(Object.assign({}, ...[...document.querySelectorAll("item")]
.filter(item => item.querySelector("post_type").textContent === "post")
.map(item => ({ [item.querySelector("link").textContent]: item.querySelector("post_id").textContent } ))));
</code></pre>
<p>which copies JSON like this to the clipboard, ready to be pasted in a JSON file (I used <code>wpids.json</code>):</p>
<pre><code class="language-json">{
...
"2022/11/tag-2": "3531",
"2023/03/contrast-ratio-new-home": "3592",
"2023/04/private-fields-considered-harmful": "3599"
}
</code></pre>
<p>Some cleanup was still needed, but this was basically good to go.</p>
<h2 id="adding-the-post-id-to-the-posts" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/preserve-disqus/#adding-the-post-id-to-the-posts">Adding the post id to the posts</a></h2>
<p>To inject a <code>wpid</code> property to each post, I added a <code>blog.11tydata.js</code> file with the following:</p>
<pre><code class="language-js">module.exports = {
eleventyComputed: {
postUrlStem: data => {
return data.page.filePathStem.replace(/^\/blog\/|\/index$/g, "");
},
wpid: data => {
return data.wpids[data.postUrlStem];
}
}
};
</code></pre>
<h2 id="linking-to-disqus" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/preserve-disqus/#linking-to-disqus">Linking to Disqus</a></h2>
<p>We now have the post id, and we can use it in our template.
Adapting the code from the <a href="https://help.disqus.com/en/articles/1717112-universal-embed-code">Universal Embed Code</a>, we get:</p>
<pre><code class="language-html">{% if wpid %}
<div id="disqus_thread"></div>
<script>
/**
* RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT
* THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR
* PLATFORM OR CMS.
*
* LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT:
* https://disqus.com/admin/universalcode/#configuration-variables
*/
var disqus_config = function () {
// Replace PAGE_URL with your page's canonical URL variable
this.page.url = 'https://lea.verou.me/{{ postUrlStem }}/';
// Replace PAGE_IDENTIFIER with your page's unique identifier variable
this.page.identifier = "{{ wpid }} https:\/\/lea.verou.me\/?p={{ wpid }}";
};
(function() { // REQUIRED CONFIGURATION VARIABLE: EDIT THE SHORTNAME BELOW
var d = document, s = d.createElement('script');
// IMPORTANT: Replace EXAMPLE with your forum shortname!
s.src = 'https://leaverou.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
{% endif %}
</code></pre>
<p>That’s it! This now works and displays the Disqus threads correctly!</p>
<h2 id="using-disqus-on-new-posts-as-well" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/preserve-disqus/#using-disqus-on-new-posts-as-well">Using Disqus on new posts as well</a></h2>
<p>Note that as it currently stands, this will not display the Disqus UI on new posts, since they won’t have a wpid.
Even if I switch to something else in the future, Disqus is better than nothing meanwhile
(for me – many people would disagree: switching to no comments at all seems very common when people switch to a SSG blog).</p>
<p>So, new posts don’t have a wpid, but they don’t need one either.
As long as we pass <em>some</em> kind of unique identifier to Disqus, we have a comment thread.
The easiest way to do this is to use the post’s path, e.g. <code>2023/preserve-disqus</code> for this one, as this is guaranteed to be unique.</p>
<p>We also want to be able to disable comments on a per-post basis, so we need a way to do that.</p>
<p>So instead of dealing with <code>wpid</code> directly in templates, I added another computed property in <code>blog.11tydata.js</code>:</p>
<pre><code class="language-js">disqus_id: data => {
let wpid = data.wpid;
if (wpid) {
return `${ wpid } https:\/\/lea.verou.me\/?p=${ wpid }`;
}
else if (data.disqus !== false) {
return typeof data.disqus !== "string"? data.postUrlStem : data.disqus;
}
}
</code></pre>
<p>Note that this allows us to pass a custom identifier to Disqus by using a string, disable it by using <code>false</code>,
or just get the automatic behavior by using <code>true</code> or not specifying it at all.
The custom identifier can be useful if we want to change the URL of a post without losing the comments.</p>
<p>Then, I updated the <a href="https://github.com/LeaVerou/lea.verou.me/blob/main/_includes/_comments.njk">template</a> to use <code>disqus_id</code> instead of <code>wpid</code>.</p>
<h2 id="what%E2%80%99s-next%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/preserve-disqus/#what%E2%80%99s-next%3F">What’s next?</a></h2>
<p>I don’t know if I will continue using Disqus.
It’s convenient, but also heavyweight, and there are <a href="https://techcrunch.com/2021/05/05/disqus-facing-3m-fine-in-norway-for-tracking-users-without-consent/?guccounter=1&guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8&guce_referrer_sig=AQAAAHcgs5Gn_4eXhly4z1yjLi-xZ4abvUmjbEhqVoOgZ_FhBpI1n7zuyLL5p16rDFxCIAq3OJDon93aKZhebse8Qy4pPfthRfLOkmBFoFImhyLr5jgmJl42mvmpmRojuqX6w3hIe9_GJl3zGTb-dVLY3ZTA-VMce3cG4UOL5xeeGNu6">privacy</a> <a href="https://fatfrogmedia.com/delete-disqus-comments-wordpress/">concerns</a> <a href="https://www.reddit.com/r/privacy/comments/egb1ey/disqus_shared_personal_data_about_tens_of/">around</a> it.</p>
<p>However, I’m not sure what I would use instead.
Any third party SaaS service would have the same privacy issues. Not necessarily now, but quite likely in the future.</p>
<p>I’ve looked into <a href="https://en.wikipedia.org/wiki/Webmention">Webmentions</a>, but the end-user experience does not compare to a regular comment system,
and it seems like quite a hassle to implement.</p>
<p><a href="https://utteranc.es/">Utterances</a> is a really cool idea: it uses GitHub issues as a backend for a comment system.
Having myself (ab)used the GitHub API as a storage backend many a times (even <a href="https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/">as early as 2012</a>), I can see the appeal.
This may be a viable path forwards, though I need to verify that GitHub Issues can be easily exported, so that I’m not locked in.</p>
<p>On a similar vein, I really loved <a href="https://giscus.app/">Gisqus</a> seems great too: It’s like Utterances, but uses GitHub Discussions instead of Issues.
What holds me back from switching to it is that Discussions cannot yet be exported,
and I think portability is important here.</p>
<p>People don’t even really use comments much anymore, they post on social media instead.
I would have <strong>loved</strong> some way to simply collect the discussions about the post from various social media and display them underneath,
but with API prices getting out of control (<a href="https://www.theverge.com/2023/3/30/23662832/twitter-api-tiers-free-bot-novelty-accounts-basic-enterprice-monthly-price">1</a> <a href="https://www.theverge.com/2023/5/31/23743993/reddit-apollo-client-api-cost">2</a>), that doesn’t seem feasible.</p>
<p>If there are any options I missed, please let me know in the (Disqus, for now 😕) comments!</p>
11ty: Index ALL the things!2023-07-19T00:00:00Zhttps://lea.verou.me/blog/2023/11ty-indices/<p>This is a second spinoff post in the <a href="https://lea.verou.me/blog/2023/going-lean/">migration saga of this blog from WordPress to 11ty</a>.</p>
<h2 id="on-good-urls" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#on-good-urls">On good URLs</a></h2>
<p>It was important to me to have good, RESTful, <a href="https://www.nngroup.com/articles/url-as-ui/">usable, hackable</a> URLs.
While a lot of that is easy and comes for free, following this principle with Eleventy proved quite hard:</p>
<blockquote>
<p>URLs that are “hackable” to allow users to move to higher levels of the information architecture by hacking off the end of the URL</p>
</blockquote>
<p>What does this mean in practice?
It means it’s not enough if <code>tags/foo/</code> shows all posts tagged “foo”, <code>tags/</code> should also show all tags.
Similarly, it’s not enough if <code>/blog/2023/04/private-fields-considered-harmful/</code> links to the <a href="https://lea.verou.me/blog/2023/04/private-fields-considered-harmful/">corresponding blog post</a>,
but also:</p>
<ul>
<li><a href="https://lea.verou.me/blog/2023/04/"><code>/blog/2023/04/</code></a> should show all posts from April 2023</li>
<li><a href="https://lea.verou.me/blog/2023/"><code>/blog/2023/</code></a> should show all posts from 2023</li>
<li><a href="https://lea.verou.me/blog/"><code>/blog/</code></a> should show all posts</li>
</ul>
<h2 id="eleventy-%E2%80%9Cpagination%E2%80%9D-primer" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#eleventy-%E2%80%9Cpagination%E2%80%9D-primer">Eleventy “Pagination” Primer</a></h2>
<p>Eleventy has a <a href="https://www.11ty.dev/docs/pagination/">pagination</a> feature, which actually does a lot more than pagination:
it’s used every time you want to generate several pages from a single template by chunking object keys and using them in permalinks.</p>
<p>One of the most common non-pagination use cases for it is tag pages. The typical <code>/tags/tagname/</code> page is generated by a deceptively simple template:</p>
<pre><code class="language-njk">---
pagination:
data: collections
size: 1
alias: tag
filter: ["blog", "all"]
permalink: /blog/tags/{{ tag }}/
override:tags: []
eleventyComputed:
title: "{{ collections[ tag ].length | pluralize('post') }} on {{ tag | format_tag }}"
---
{% set taglist = collections[ tag ] | reverse %}
{# ... Loop over taglist here ... #}
</code></pre>
<p>That was it, then you just loop over <code>taglist</code> (or <code>collections[ tag ] | reverse</code> directly) to template the posts under each tag in reverse chronological order.
Simple, right?
But what about the indices?
As it currently stands, visiting <code>/blog/tags/</code> will just produce a 404.</p>
<h2 id="index-of-all-tags" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#index-of-all-tags">Index of all tags</a></h2>
<p>Creating an index of all tags only involves a single page, so it does not involve contorting the pagination feature to mind-bending levels,
like the rest of this post.
However, we need to do some processing to sort the tags by post count, and remove those that are not “real” tags.</p>
<p>There are many ways to go about with this.</p>
<h3 id="the-quick-and-dirty-way" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#the-quick-and-dirty-way">The quick and dirty way</a></h3>
<p>The quick and dirty way is to just iterate over <code>collections</code> and count the posts for each tag:</p>
<pre><code class="language-njk"><ol>
{% for tag, posts in collections %}
<li>{{ tags.one(tag) }}
({{ posts.length }} posts)
</li>
{% endfor %}
</ol>
</code></pre>
<div class="tip">
<p>Unfamiliar with the <code>tags.one()</code> syntax above?
It’s using <a href="https://mozilla.github.io/nunjucks/templating.html#macro">Nunjucks macros</a> (there’s a <code>{% import "_tags.njk" as tags %}</code> earlier in the template too).
Macros allow you to create parameterized templates snippets,
and I’ve come to love them during this migration project.</p>
</div>
<p>The problem is that this does not produce the tags in any particular order,
and you usually want frequently used tags to come first.
You could actually fix that with CSS:</p>
<pre><code class="language-njk"><ol>
{% for tag, posts in collections %}
<li style="order: {{ collections.all.length - posts.length }}">
{{ tags.one(tag) }}
({{ posts.length }} posts)
</li>
{% endfor %}
</ol>
</code></pre>
<p>The only advantage of this approach is that this is entirely doable via templates and doesn’t require any JS,
but there are several drawbacks.
First, it limits what styling you can use: for the <code>order</code> property to actually have an effect, you need to be using either Flexbox or Grid layout.
But worse, the <code>order</code> property <a href="https://www.w3.org/TR/css-flexbox-1/#order-accessibility">does not affect the order screen readers read your content</a> one iota.</p>
<h3 id="dynamic-postsbytag-collection" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#dynamic-postsbytag-collection">Dynamic <code>postsByTag</code> collection</a></h3>
<p>To do it all in Eleventy, <a href="https://github.com/11ty/eleventy/issues/927">the most common way</a> is a dynamic collection,
added via <a href="https://www.11ty.dev/docs/collections/#advanced-custom-filtering-and-sorting"><code>eleventyConfig.addCollection()</code></a>:</p>
<pre><code class="language-js">config.addCollection("postsByTag", (collectionApi) => {
const posts = collectionApi.getFilteredByTag("blog");
let ret = {};
for (let post of posts) {
for (let tag of post.data.tags) {
ret[tag] ??= [];
ret[tag].push(post);
}
}
// Now sort, and reconstruct the object
ret = Object.fromEntries(Object.entries(ret).sort((a, b) => b[1].length - a[1].length));
return ret;
});
</code></pre>
<p>That we then use in the template:</p>
<pre><code class="language-njk"><ol>
{% for tag, posts in collections.postsByTag %}
<li>
{{ tags.one(tag) }} ({{ posts }} posts)
</li>
{% endfor %}
</ol>
</code></pre>
<h3 id="custom-taglist-filter" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#custom-taglist-filter">Custom <code>taglist</code> filter</a></h3>
<p>Another way is a custom filter:</p>
<pre><code class="language-js">config.addFilter("taglist" (collections) => {
let tags = Object.keys(collections).filter(filters.is_real_tag);
tags.sort((a, b) => collections[b].length - collections[a].length);
return Object.fromEntries(tags.map(tag => [tag, collections[tag].length]));
});
</code></pre>
<p>used like this:</p>
<pre><code class="language-njk"><ol>
{% for tag, posts in collections | taglist %}
<li>
{{ tags.one(tag) }} ({{ posts }} posts)
</li>
{% endfor %}
</ol>
</code></pre>
<p>Usually, filters are meant for more broadly usable utility functions, and are not a good fit here.
However, the filter approach can be more elegant if your use case is more complicated and involves many different outputs.
For the vast majority of use cases, a dynamic collection is more appropriate.</p>
<h2 id="index-of-posts-by-year" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#index-of-posts-by-year">Index of posts by year</a></h2>
<p>Generating yearly indices can be quite similar as generating tag pages.
The main difference is that for tags the collection already exists (<code>collections[tag]</code>) whereas
for years you have to build it yourself, using <code>addCollection()</code> in your config file.</p>
<p>This seems to come up pretty frequently, both for years and months (the next section):</p>
<ul>
<li><a href="https://github.com/11ty/eleventy/issues/502">https://github.com/11ty/eleventy/issues/502</a></li>
<li><a href="https://github.com/tomayac/blogccasion/issues/19">https://github.com/tomayac/blogccasion/issues/19</a></li>
<li><a href="https://github.com/11ty/eleventy/issues/316#issuecomment-441053919">https://github.com/11ty/eleventy/issues/316#issuecomment-441053919</a></li>
<li><a href="https://github.com/11ty/eleventy/issues/1284">https://github.com/11ty/eleventy/issues/1284</a></li>
<li><a href="https://darekkay.com/blog/eleventy-group-posts-by-year/">Group posts by year in Eleventy (Blog post)</a></li>
</ul>
<p>This is what I did, after spending a pretty long time reading discussions and blog posts:</p>
<pre><code class="language-js">eleventyConfig.addCollection("postsByYear", (collectionApi) => {
const posts = collectionApi.getFilteredByTag("blog").reverse();
const ret = {};
for (let post of posts) {
let key = post.date.getFullYear();
ret[key] ??= [];
ret[key].push(post);
}
return ret;
});
</code></pre>
<p>and then, in <a href="https://github.com/LeaVerou/lea.verou.me/tree/main/blog/year-index.njk"><code>blog/year-index.njk</code></a>:</p>
<pre><code class="language-njk">---
pagination:
data: collections.postsByYear
size: 1
alias: year
permalink: /blog/{{ year }}/
override:tags: []
eleventyComputed:
title: "Posts from {{ year }}"
---
{% import "_posts.njk" as posts %}
{{ posts.list(collections.postsByYear[year], {style: "compact"}) }}
</code></pre>
<p>You can see an example of such a page here: <a href="https://lea.verou.me/blog/2010/">Posts from 2010</a>.</p>
<p>Bonus, because this collection is more broadly useful, I was able to utilize it to make a little yearly archives bar chart!</p>
<p><img src="https://lea.verou.me/blog/2023/11ty-indices/image.png" alt="Bar chart of posts by year, with 2012 selected and text above it reading "28 posts"" /></p>
<h2 id="index-of-posts-by-month" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/11ty-indices/#index-of-posts-by-month">Index of posts by month</a></h2>
<p>Pagination only works on one level:
You cannot paginate a paginated collection (though Zach has a <a href="https://github.com/11ty/eleventy/issues/332#issuecomment-445236776">workaround</a> for that that I’m still trying to wrap my head around).
This also means that you cannot easily paginate tag or year index pages.
I worked around that by simply showing a more compact post list if there are more than 10 posts.</p>
<p>However, it also means you cannot process the <code>postsByYear</code> collection and somehow paginate by month.
You need to create <em>another</em> collection, this time with the year + month as the key:</p>
<pre><code class="language-js">config.addCollection("postsByMonth", (collectionApi) => {
const posts = collectionApi.getFilteredByTag("blog").reverse();
const ret = {};
for (let post of posts) {
let key = filters.format_date(post.date, "iso").substring(0, 7); // YYYY-MM
ret[key] ??= [];
ret[key].push(post);
}
return ret;
});
</code></pre>
<p>And a separate [<code>blog/month-index.njk</code>]({{ site.repo_file }}/blog/month-index.njk) file:</p>
<pre><code class="language-njk">---
pagination:
data: collections.postsByMonth
size: 1
alias: month
permalink: /blog/{{ month | replace("-", "/") }}/
override:tags: []
eleventyComputed:
title: "Posts from {{ month | format_date({month: 'long', year: 'numeric'}) }}"
---
{% import "_posts.njk" as posts %}
{{ posts.list(collections.postsByMonth[month], {style: "compact"}) }}
</code></pre>
<p>You can see an example of such a page here: <a href="https://lea.verou.me/blog/2010/12/">Posts from December 2010</a>.</p>
Rethinking Categorization2023-07-20T00:00:00Zhttps://lea.verou.me/blog/2023/rethinking-categorization/<p>This is the third spinoff post in the <a href="https://lea.verou.me/blog/2023/going-lean/">migration saga of this blog from WordPress to 11ty</a>.</p>
<p>Migrating was a good opportunity to <a href="https://twitter.com/LeaVerou/status/1680900090829983744">rethink the information architecture of my site</a>,
especially around categorization.</p>
<h2 id="categories-vs-tags" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#categories-vs-tags">Categories vs Tags</a></h2>
<p>Just like most WP users, I was using both categories and tags, simply because they came for free.
However the difference between them was a bit fuzzy, as evidenced by how inconsistently they are used, both here and around the Web.
I was mainly using Categories for the <em>type</em> of article (Articles, Rants, Releases, Tips, Tutorials, News, Thoughts),
however there were also categories that were more like content tags (e.g. CSS WG, Original, Speaking, Benchmarks).</p>
<p>This was easily solved by moving the latter to actual tags.
However, tags are no panacea, there are several issues with them as well.</p>
<h2 id="problems-with-tags" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#problems-with-tags">Problems with tags</a></h2>
<h3 id="tag-aliases" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#tag-aliases">Tag aliases</a></h3>
<p>First, there were many tags that were <strong>synonyms of each other</strong>, and posts were fragmented across them, or had to include both (e.g. <a href="https://lea.verou.me/blog/tags/js/">JS</a> and <a href="https://lea.verou.me/blog/tags/javascript">Javascript</a>).
I addressed this by defining <a href="https://github.com/LeaVerou/lea.verou.me/tree/main/data/tag_aliases.json">aliases</a> in a global data file, and using Eleventy to <a href="https://github.com/LeaVerou/lea.verou.me/tree/main/redirects.njk">dynamically build Netlify redirects</a> for them.</p>
<pre><code data-file="redirects.njk" class="language-njk"># Tag aliases
{% for alias, tag in tag_aliases %}/tags/{{ alias }}/ /tags/{{ tag }}/ 301
{% endfor %}
</code></pre>
<p>Turns out I’m not the first to think of building the Netlify <code>_redirects</code> file dynamically, some googling revealed <a href="https://www.aleksandrhovhannisyan.com/blog/eleventy-netlify-redirects/">this blog post</a> from 2021 that does the same thing.</p>
<p>I’ve also decided to expose these aliases in the <a href="https://lea.verou.me/blog/tags/">tags index</a>:</p>
<p><img src="https://lea.verou.me/blog/2023/rethinking-categorization/images/aliases-tag-index.png" alt="" /></p>
<h3 id="%E2%80%9Corphan%E2%80%9D-tags" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#%E2%80%9Corphan%E2%80%9D-tags">“Orphan” tags</a></h3>
<p>Lastly, another issue is what I call “orphan tags”: Tags that are only used in a single post.
The primary use case for both tags and categories is to help you discover related content.
Tags that are only used once clutter the list of tags, but serve no actual purpose.</p>
<p>It is important to note that orphan tags are not (always) an authoring mistake.
While some tags are definitely too specific and thus unlikely to be used again,
the vast majority of orphan tags are tags that <em>could</em> plausibly be used again, but it simply hasn’t happened.</p>
<p>I definitely removed a bunch of overly specific tags from the content,
but was still left with more orphan tags than tags with more than one post (103 vs 78 as I write these lines).</p>
<p>For (1), the best course of action is probably to remove the tags from the content altogether.
However for (2), there are two things to consider.</p>
<h4 id="how-to-best-display-orphan-tags-in-the-tag-index%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#how-to-best-display-orphan-tags-in-the-tag-index%3F">How to best display orphan tags in the <a href="https://lea.verou.me/blog/tags/">tag index</a>?</a></h4>
<p>For the <a href="https://lea.verou.me/blog/tags/">tag index</a>, I’ve separated orphan tags from the rest,
and I’m displaying them in a <code><details></code> element at the end, that is collapsed by default.</p>
<p><img src="https://lea.verou.me/blog/2023/rethinking-categorization/images/orphan-index.png" alt="" /></p>
<p>Each tag is a link to the post that uses it instead of a tags page, since there is only one post that uses it.</p>
<h4 id="how-to-best-display-orphan-tags-in-the-post-itself%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#how-to-best-display-orphan-tags-in-the-post-itself%3F">How to best display orphan tags in the post itself?</a></h4>
<p>This is a little trickier.
For now, I’ve refrained from making them links, and I’m displaying them faded out to communicate this.</p>
<p><img src="https://lea.verou.me/blog/2023/rethinking-categorization/images/orphan-tags-post.png" alt="" /></p>
<p>Another alternative I’m contemplating is to hide them entirely.
Not as a punitive measure because they have failed at their one purpose in life 😅, but because this would allow me to use tags liberally,
and only what sticks would be displayed to the end user.</p>
<p>A third, intermediate solution, would be to have a “and 4 orphan tags” message at the end of the list of tags, which can be clicked to show them.</p>
<p>These are not just UX/IA improvements, they are also performance improvements.
<strong>Not linking orphan tags to tag pages means I don’t need to generate these tag pages at all.</strong>
Since the majority of tags are orphan tags, this allowed me to substantially reduce the number of pages that need to be generated,
and cut down build time by a whopping <strong>40%</strong>, from 2.7s to 1.7s (on average).</p>
<h3 id="tag-hierarchies%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#tag-hierarchies%3F">Tag hierarchies?</a></h3>
<p>The theory is that categories are a <a href="https://en.wikipedia.org/wiki/Taxonomy">taxonomy</a> and tags a <a href="https://en.wikipedia.org/wiki/Folksonomy">folksonomy</a>.
Taxonomies can be hierarchical, but folksonomies are, <a href="https://en.wikipedia.org/wiki/Folksonomy#Folksonomy_vs._taxonomy">by definition</a>, flat.
However, <strong>in practice, tags almost always have an implicit hierarchy</strong>, which is also what <a href="https://en.wikipedia.org/wiki/Folksonomy#Folksonomy_vs._taxonomy">research on folksonomies in the wild tends to find</a>.</p>
<p>Examples from this very blog:</p>
<ul>
<li>There is a separate tag for <a href="https://lea.verou.me/blog/tags/es/">ES</a> (ECMAScript), and a separate one for <a href="https://lea.verou.me/blog/tags/js">JS</a>.
However, any post tagged ES should also be tagged JS – though the opposite is not true.</li>
<li>There is a tag for <a href="https://lea.verou.me/blog/tags/css/">CSS</a>, tags for specific CSS specifications (e.g. <a href="https://lea.verou.me/blog/tags/css-backgrounds/">CSS Backgrounds & Borders</a>), and even tags for specific CSS functions or properties (e.g. <a href="https://lea.verou.me/blog/tags/background-attachment/"><code>background-attachment</code></a>, <a href="https://lea.verou.me/blog/tags/background-size/"><code>background-size</code></a>).
However, these are not orthogonal: posts tagged with specific CSS features should also be tagged with the CSS spec that contains them, as well as a general “CSS” tag.</li>
</ul>
<p><strong>I have yet to see a use case for tagging that does <em>not</em> result in implicit hierarchies.</strong>
Yet, all UIs for entering tags assume that they are flat.
Instead, it’s up to each individual post to maintain these relationships, which is tedious and error prone.
In practice, the more general tags are often left out, but not intentionally or predictably.</p>
<p>It would be much better to be able to define this hierarchy in a central place, and have it automatically applied to all posts.
In 11ty, it could be as simple as a data file for each tag’s “parent” tag.
Every time the tag is used, its parent is also added to the post automatically, recursively all the way up to the root (at build time).
I have not tried this yet, but I’m excited to experiment with it once I have a bit more time.</p>
<h2 id="categories-vs-tags%3A-reprise" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/rethinking-categorization/#categories-vs-tags%3A-reprise">Categories vs Tags: Reprise</a></h2>
<p>Back to our original dilemma: Do I still need categories, especially if I eventually implement tag hierarchies?
It does seem that the categories I used in WP for the article type (Articles, Rants, Releases, Tips, Tutorials, News, Thoughts etc)
are somewhat distinct from my usage of tags, which are more about the content of the article.
However, it is unclear whether this is the best use of categories, or whether I should just use tags for this as well.
Another common practice is to use tags for more specific content tags, and categories for broader areas (e.g. “Software engineering”, “Product”, “HCI”, “Personal” etc).
Skipping past the point that tag hierarchies make it easy to use tags for this too, this makes me think: maybe what is needed is actually metadata, not categories.
Instead of deciding that categories hold the article type, or the broader domain, what if we had certain attributes for <em>both</em> of these things.
Then, we could have a “type” attribute, and a “domain” attribute, and use them both for categorization, and for filtering.
Since Eleventy already supports arbitrary metadata, this is just a matter of implementation.</p>
<p>Lots to think about, but one thing seems clear: Categories do not have a clear purpose, and thus I’m doing away with them.
For now, I have converted all past categories to tags, so that the additional metadata is not lost,
and I will revisit how to best expose this metadata in the future.</p>
Going Lean2023-07-21T00:00:00Zhttps://lea.verou.me/blog/2023/going-lean/<p>WordPress has been with me since <a href="https://lea.verou.me/blog">my very first post in 2009</a>.
There is a lot to love about it: It’s open source, it has a thriving ecosystem, a beautiful default theme, and a revolutionary block editor that makes my inner UX geek giddy.
Plus, WP made building a website and publishing content accessible to everyone.
No wonder it’s <a href="https://almanac.httparchive.org/en/2022/cms#most-popular-cmss">the most popular CMS in the world</a>, by a <strong>huge</strong> margin.</p>
<p>However, for me, the bad had started to outweigh the good:</p>
<ul>
<li>Things I could do in minutes in a static site, in WP required finding a plugin or tweaking PHP code.</li>
<li>It was slow and bloated.</li>
<li>Getting a draft out of it and into another medium was a pain.</li>
<li>Despite having never been hacked, I was terrified about it, given all the horror stories.</li>
<li>I was periodically getting “Error establishing a database connection” errors, whose frequency kept increasing.</li>
</ul>
<p>It was time to move on.
<em>It’s not you WP, it’s me.</em></p>
<p>It seemed obvious that the next step would be a statically generated blog.
I had been using <a href="https://11ty.dev/">Eleventy</a> for a while on a variety of sites at that point and loved it, so using that was a no-brainer.
In fact, my blog was one of my last remaining non-JAMstack sites, and by far the biggest.
I had built a <a href="https://svgees.us/blog/">simple 11ty blog for my husband</a> a year ago, and was almost jealous of the convenience and simplicity.
There are so many conveniences that just come for free with this workflow: git, Markdown, custom components, even GitHub Copilot as you write your prose!
And if you can make the repo public, oooooh, the possibilities! People could even file PRs and issues for your blog posts!</p>
<p>Using <a href="https://netlify.com/">Netlify</a> as a platform was also a no-brainer:
I had been using it for years, for over 30 sites at this point!
I love their simplicity, their focus on developer experience, and their commitment to open source.
I also happen to know a bunch of folks there, and they have a great culture too.</p>
<p>However, I was dreading the amount of work it would take to migrate 14 years of content, plugins, and styling.
The stroke that broke the camel’s back was a particularly bad db outage.
I <a href="https://twitter.com/LeaVerou/status/1652166572335587329">tweeted</a> about my frustration, but I had already made up my mind.</p>
<p>I reviewed the list of plugins I had installed on WP to estimate the amount of work.
Nearly all fell in one of two categories:</p>
<ol>
<li>Solving problems I wouldn’t have if I wasn’t using WP (e.g. SVG support, Don’t Muck My Markup)</li>
<li>Giving me benefits I could get in 11ty with very little code (e.g. Prism syntax highlighting, Custom Body Class, Disqus, Unlist Posts & Pages, Widget CSS classes)</li>
<li>Giving me benefits I could get with existing Eleventy plugins (e.g. Add Anchor Links, Easy Table of Contents)</li>
</ol>
<p>This could actually work!</p>
<h2 id="public-or-private-repo%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#public-or-private-repo%3F">Public or private repo?</a></h2>
<p>One of the hardest dilemmas was whether to make the repo for this website public or private.</p>
<p>Overall, I was happy to have most files be public, but there were a few things I wanted to keep private:</p>
<ul>
<li>Drafts (some drafts I’m ok to share publicly, but not all)</li>
<li>Unlisted pages and posts (posts with publicly accessible URLs, but not linked from anywhere)</li>
<li>Private pages (e.g. in the previous site I had a password-protected page with my details for conference organizers)</li>
</ul>
<p>Unfortunately, right now it’s all-or-nothing, even if only one file needs to be private, the whole repo needs to be private.</p>
<aside>
<p>FWIW I don’t think it has to be this way, and I <a href="https://twitter.com/LeaVerou/status/1652806575973605378">tweeted</a> about this,
including some ideas about fixing it, either from the GitHub side, or the serverless platform side.
I’m hoping to write a blog post to expand on this soon.</p>
</aside>
<p>Making the repo public does have many advantages:</p>
<ul>
<li>Transparency is one of my core values, and this is in line with it.</li>
<li>People can learn from my code and avoid going down the same rabbit holes I did.</li>
<li>People can file issues for problems.</li>
<li>People can send PRs to fix both content and functionality.</li>
<li>I wouldn’t need to use a separate public repo for the data that populates my <a href="https://lea.verou.me/speaking/">Speaking</a>, <a href="https://lea.verou.me/publications/">Publications</a>, and <a href="https://lea.verou.me/projects/">Projects</a> pages.</li>
</ul>
<p>I went back and forth quite a lot, but in the end I decided to make it public.
In fact, I fully embraced it, by making it as easy as possible to file issues and submit PRs.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/going-lean/images/broken-page-notice.png" alt="Notice from top of page saying "You are browsing the new, beta version of my website. Some things may not work properly. View this page on the old website and if you spot any problems, please file an issue!" with links throughout" /></p>
<figcaption>
<p>Each page has a link to report a problem with it, which prefills as much info as possible.
This was also a good excuse to try out <a href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms">GitHub Issue Forms</a>,
as well as <a href="https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue#creating-an-issue-from-a-url-query">URLs for prefilling the form</a>!</p>
</figcaption>
</figure>
<figure>
<p><img src="https://lea.verou.me/blog/2023/going-lean/images/gh-edit.png" alt=""Edit on GitHub link"" /></p>
<figcaption>
Each page has a link to edit it on GitHub, which automatically takes you through a PR flow if you don’t have write access to the repo.
</figcaption>
</figure>
<h3 id="licensing" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#licensing">Licensing</a></h3>
<p>Note that <strong>a public repo is not automatically open source</strong>.
As you know, I have a long track record of open sourcing my code.
I love seeing people learning from it, using it in their own projects, and blogging about what they’ve learned.
So, <a href="https://opensource.org/licenses/MIT">MIT-licensing</a> the <strong>code</strong> part of this website is a no-brainer.
<a href="https://creativecommons.org/licenses/by/4.0/">CC-BY</a> also seems like a no-brainer for <strong>content</strong>, because, why not?</p>
<p>Where it gets tricky is the <strong>design</strong>.
I’m well aware that neither my logo nor the visual style of this website would win any design awards;
I haven’t worked as a graphic designer for many years, and it shows.
However, it’s something I feel is very personal to me, my own personal brand, which by definition needs to be unique.
Seeing another website with the same logo and/or visual style would feel just as unsettling as walking into a house that looks exactly like mine.
I’m speaking from experience: I’ve had my logo and design copied many times, and it always felt like a violation.</p>
<p>I’m not sure how to express this distinction in a GitHub <code>LICENSE</code> file, so I haven’t yet added one,
but I did try to outline it in the <a href="https://lea.verou.me/credits/#licensing">Credits & Making Of</a> page.</p>
<p>It’s still difficult to draw the line precisely, especially when it comes to CSS code.
I’m basically happy for people to copy as much of my CSS code as they want (following <a href="https://opensource.org/licenses/MIT">MIT license</a> rules),
as long as the end result doesn’t scream “Lea Verou” to anyone who has seen this site.
<em>But how on Earth do you express that?</em> 🤔</p>
<h2 id="migrating-content-to-markdown" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#migrating-content-to-markdown">Migrating content to Markdown</a></h2>
<p>The title of this section says “to Markdown” because that’s one of the benefits of this approach:
static site generators are largely compatible with each other, so if I ever needed to migrate again, it would be much easier.</p>
<p>Thankfully, travelers on this road before me had already paved it.
Many open source scripts out there to migrate WP to Markdown!
The one that worked well for me was <a href="https://github.com/lonekorean/wordpress-export-to-markdown">lonekorean/wordpress-export-to-markdown</a>
(though I later discovered there’s <a href="https://github.com/flowershow/wordpress-to-markdown">a more updated fork</a> now)</p>
<p>It was still a bumpy road. First, it kept getting stuck on parsing the WP XML export, specifically in comments.
I use Disqus for comments, but it mirrors comments in the internal WP system.
Also, WP seems to continue recording trackbacks even if they are not displayed anywhere.
Turns out I had hundreds of thousands of spam trackbacks, which I spent hours cleaning up (it was quite a meditative experience).
In the end I got the total comments + trackbacks from 290K down to 26K which reduced the size of the XML export from 210 MB to a mere 31 MB.
This did not fix the parsing issue, but allowed me to simply open the file in VS Code and delete the problematic comments manually.
It also fixed the uptime issues I was having: I never got another “Error establishing a database connection” error after that, despite taking my own sweet time to migrate (started in April 2023, and finished in July!).
Ideally, I wish WP had an option to export without comments, but I guess that’s not a common use case.</p>
<p>While this importer is great, and allowed me to configure the file structure in a way that preserved all my URLs, I did lose a few things:</p>
<ul>
<li>“Read more” separators (filed it as <a href="https://github.com/lonekorean/wordpress-export-to-markdown/issues/93">issue #93</a>)</li>
<li>Figures (they are imported as just images with text underneath) (filed it as <a href="https://github.com/lonekorean/wordpress-export-to-markdown/issues/94">issue #94</a>)</li>
<li>Drafts (<a href="https://github.com/lonekorean/wordpress-export-to-markdown/issues/16">#16</a>)</li>
<li>Pages (I had to manually copy them over, but it was only a handful)</li>
<li>Any custom classes were gone (e.g. a <code>"view-demo"</code> class I used to create “call to action” links)</li>
</ul>
<p>A few other issues:</p>
<ul>
<li>It downloaded all images, but did not update the URLs in the Markdown files.
This was easy to fix with a regex find and replace from <code>https?://lea.verou.me/wp-content/uploads/(\d{4}/\d{2})/([\w\.-]+\.(?:png|gif|jpe?g))</code> to <code>images/$2</code>.</li>
<li>Some images from some posts were not downloaded – I still have no idea why.</li>
<li>It did not download any non-media uploads, e.g. zip files.
Thankfully, these were only a couple, so I could detect and port over manually.</li>
<li>Older posts included code directly in the content, without code blocks, which meant it was being parsed as HTML, often with disastrous results (e.g. the post just cutting off in the middle of a sentence because it mentioned <code><script></code>, which opened an actual <code><script></code> element and ate up the rest of the content).
I fixed a few manually, but I’m sure there’s more left.</li>
<li>Because code was just included as content, the importer also escaped all Markdown special symbols, so adding code blocks around it was not enough, I also had to remove a bunch of backslashes manually.</li>
</ul>
<h2 id="rethinking-categorization" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#rethinking-categorization">Rethinking Categorization</a></h2>
<p>While the importer preserved both tags and categories, this was a good opportunity to rethink whether I need them both,
and to re-evaluate how I use them.</p>
<p>This spun off into a separate post: <a href="https://lea.verou.me/blog/2023/rethinking-categorization/">Rethinking Categorization</a>.</p>
<h2 id="migrating-comments" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#migrating-comments">Migrating comments</a></h2>
<p>Probably one of the hardest parts of this migration was preserving Disqus comments.
In fact, it was so hard that I procrastinated on it for three months,
being stuck in a limbo where I couldn’t blog because I’d have to port the new post manually.</p>
<p>I’ve documented the process in a <a href="https://lea.verou.me/blog/2023/preserve-disqus/">separate blog post</a>, as it was quite involved,
including some thoughts about what system to use in the future, as I eventually hope to migrate away from Disqus.</p>
<h2 id="keeping-urls-cool" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#keeping-urls-cool">Keeping URLs cool</a></h2>
<p>I wanted to preserve the URL structure of my old site as much as possible, both for SEO, but also because <a href="https://www.w3.org/Provider/Style/URI">cool URLs don’t change</a>.
The WP importer I used allowed me to preserve the <code>/year/month/slug</code> structure of my URLs.</p>
<p>I did want to have the blog in its own directory though.
This site started as a blog, but I now see it as more of a personal site <em>with</em> a blog.
Thankfully, redirecting these URLs to corresponding <code>/blog/</code> URLs was a one liner using <a href="https://docs.netlify.com/routing/redirects/">Netlify redirects</a>:</p>
<pre><code>/20* /blog/20:splat 301
</code></pre>
<p>Going forwards, I also decided to do away with the month being part of the URL, as it complicates the file structure for no discernible benefit and I don’t blog nearly as much now as I did in 2009, e.g. compare <a href="https://lea.verou.me/blog/2009">2009</a> vs <a href="https://lea.verou.me/blog/2022/">2022</a>: 38 vs 7!
I do think I will start blogging more again now, not only due to the new site,
but also due to new interests and a long backlog of ideas (just look at <a href="https://lea.verou.me/blog/2023/07/">July 2023</a> so far!).
However, I doubt I will ever get back to the pre-2014 levels, I simply don’t have that kind of time anymore
(coincidentally, it appears my blogging frequency dropped significantly after I <a href="https://lea.verou.me/blog/2014/02/im-going-to-mit/">started my PhD</a>).</p>
<p>I also wanted to continue having nice, RESTful, <a href="https://www.nngroup.com/articles/url-as-ui/">usable</a> URLs, which also requires:</p>
<blockquote>
<p>URLs that are “hackable” to allow users to move to higher levels of the information architecture by hacking off the end of the URL</p>
</blockquote>
<p>In practice, this means it’s not enough if <code>tags/foo/</code> shows all posts tagged “foo”, <code>tags/</code> should also show all tags.
Similarly, it’s not enough if <code>/blog/2023/04/private-fields-considered-harmful/</code> links to the <a href="https://lea.verou.me/blog/2023/04/private-fields-considered-harmful/">corresponding blog post</a>,
but also:</p>
<ul>
<li><a href="https://lea.verou.me/blog/2023/04/"><code>/blog/2023/04/</code></a> should show all posts from April 2023</li>
<li><a href="https://lea.verou.me/blog/2023/"><code>/blog/2023/</code></a> should show all posts from 2023</li>
<li>and of course <a href="https://lea.verou.me/blog/"><code>/blog/</code></a> should show all posts</li>
</ul>
<p>This proved quite tricky to do with Eleventy, and spanned an entirely different <a href="https://lea.verou.me/blog/2023/11ty-indices/">blog post</a>.</p>
<h2 id="overall-impressions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#overall-impressions">Overall impressions</a></h2>
<p>Overall, I’m happy with the result, and the flexibility.
I’ve had a lot of fun with this project, and it was a great distraction during a very difficult time in my life,
due to dealing with some serious health issues in my immediate family.</p>
<p>However, there are a few things that are now more of a hassle than they were in WP,
mainly around the editing flow:</p>
<ul>
<li>In WP, editing a blog post I was looking at in my browser was a single click (provided I was logged in).
I guess I could still do that by editing through GitHub, but now I’m spoiled, I want an easy way to edit in my own editor
(VS Code, which has <a href="https://code.visualstudio.com/docs/languages/markdown">a lot of nice features for Markdown editing</a>),
however the only way to do that is to either painfully traverse the directory structure, or …search to find the right *.md file,
neither of which is ideal.</li>
<li>Previewing a post I was editing was also a single click, whereas now I need to run a local server and manually type the URL in (or browse the website to find it).</li>
<li>Making edits now requires me to think of a suitable commit message.
Sure, this is useful sometimes, but most of the time, I want the convenience of just saving my changes and being done with it.</li>
</ul>
<h3 id="open-file-in-vs-code-from-the-browser%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#open-file-in-vs-code-from-the-browser%3F">Open file in VS Code from the browser?</a></h3>
<p>There <em>is</em> a way to solve the first problem: VS Code supports a <code>vscode://</code> protocol that allows you to
<a href="https://code.visualstudio.com/docs/editor/command-line#_opening-vs-code-with-urls">open a file in VS Code from the browser</a>.
This means, this link would open the file for this blog post in VS Code:</p>
<pre><code class="language-html"><a href="vscode://file/Users/leaverou/Documents/lea.verou.me/blog/2023/going-lean/index.md">Edit in VS Code</a>
</code></pre>
<p>See the issue? I cannot add a button to the UI that only works for me!
However, I don’t <em>need</em> to add a button to the UI:
as long as I expose the input path of the current page (Eleventy’s <a href="https://www.11ty.dev/docs/data-eleventy-supplied/"><code>page.inputPath</code></a>) in the HTML somehow,
I can just add a bookmarklet to my own browser that just does:</p>
<pre><code class="language-js">location.href = `vscode://file/Users/leaverou/Documents/lea.verou.me/${document.documentElement.dataset.inputpath}`;
</code></pre>
<p>In fact, here it is, ready to be dragged to the bookmarks bar:
<a href="javascript:location.href = `vscode://file/Users/leaverou/Documents/lea.verou.me/${document.documentElement.dataset.inputpath}`" class="call-to-action">Edit in VS Code</a></p>
<p>Now, if only I could find a way to do the opposite: open the localhost URL that corresponds to the Markdown file I’m editing — and my workflow would be complete!</p>
<h2 id="what%E2%80%99s-next%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/going-lean/#what%E2%80%99s-next%3F">What’s next?</a></h2>
<p>Obviously, there’s a lot of work left to do, and I bet people will find a lot more breakage than I had noticed.
I also have a backlog of blog post ideas that I can’t <em>wait</em> to write about.</p>
<p>But I’ve also been toying around with the idea of porting over my personal (non-tech) blog posts,
and keep them in an entirely separate section of the website.
I don’t like that my content is currently hostage to <a href="https://pensieve.verou.me/">Tumblr</a> (2012-2013) and <a href="https://leaverou.medium.com/">Medium</a> (2017-2021),
and would love to own it too,
though I’m a bit concerned that properly separating the two would take a lot of work.</p>
<p>Anyhow, 'nuff said. Ship it, squirrel! 🚢🐿️</p>
Help Design the Inaugural State of HTML Survey!2023-07-26T00:00:00Zhttps://lea.verou.me/blog/2023/design-state-of-html/<p>You have likely participated in several <a href="https://survey.devographics.com/en-US">Devographics surveys</a> before,
such as <a href="https://stateofcss.com/en-us/">State of CSS</a>, or <a href="https://stateofjs.com/en-us/">State of JS</a>.
These surveys have become the primary source of unbiased data for the practices of front-end developers today
(there is also the <a href="https://almanac.httparchive.org/">Web Almanac</a> research, but because this studies what is actually used on the web, it takes a lot longer for changes in developer practices to propagate).</p>
<p>You may remember that last summer, <a href="https://lea.verou.me/blog/2022/07/help-design-the-state-of-css-survey-2022/">Google sponsored me to be Survey Design Lead</a> for <a href="https://survey.devographics.com/en-US/survey/state-of-css/2022">State of CSS 2022</a>.
It went really well: we got 60% higher response rate than the year before, which gave browsers a lot of actionable data to prioritize their work.
The feedback from these surveys is a prime input into the <a href="https://web.dev/interop-2023/">Interop</a> project,
where browsers collaborate to implement the most important features for developers interoperably.</p>
<p>So this summer, Google trusted me with a much bigger project, a brand new survey: <strong>State of HTML</strong>!</p>
<p>For some of you, a State of HTML survey may be the obvious next step, the remaining missing piece.
For others, the gap this is filling may not be as clear.
No, this is not about whether you prefer <code><div></code> or <code><span></code>!
It turns out, just like JavaScript and CSS, HTML is actually going through an evolution of its own!
New elements like <a href="https://github.com/Devographics/surveys/discussions/95"><code><selectmenu></code></a> and <a href="https://github.com/Devographics/surveys/discussions/116"><code><breadcrumb></code></a> are on the horizon, or cool new features like <a href="https://github.com/Devographics/surveys/discussions/138">popovers</a> and <a href="https://github.com/Devographics/surveys/discussions/100">declarative Shadow DOM</a>.
There are even JS APIs that are intrinsically tied to HTML, such as e.g. <a href="https://github.com/Devographics/surveys/discussions/98">Imperative slot assignment</a>
or DOM APIs like <a href="https://github.com/Devographics/surveys/discussions/153"><code>input.showPicker()</code></a>
Historically, these did not fit in any of these surveys.
<a href="https://2022.stateofjs.com/en-US/features/browser-apis/#custom_elements">Some</a> were previously asked in <a href="https://stateofjs.com/">State of JS</a>, <a href="https://2021.stateofcss.com/en-US/features/accessibility/#tabindex">some</a> in <a href="https://stateofcss.com/">State of CSS</a>, but it was always a bit awkward.
This new survey aims to fill these gaps, and finish surveying the core technologies of the Web, which are HTML, CSS and JavaScript.</p>
<p>Designing a brand new survey is a more daunting task than creating the new edition of an existing survey,
but also an exciting one, as comparability with the data from prior years is not a concern,
so there is a lot more freedom.</p>
<p>Each <em>State of X</em> survey consists of two parts:
<dfn id="part1">Part 1</dfn> is a quiz: a long list of lesser-known and/or cutting-edge (or even upcoming) features where respondents select one of three options:</p>
<p><img src="https://lea.verou.me/blog/2023/design-state-of-html/images/feature-example.png" alt="Screenshot of question saying "Custom Elements" with answers 🤷 Never heard of it/Not sure what it is, ✅ Know what it is, but haven't used it, 👍 I've used it" />
Starting with State of CSS 2022, respondents could also add freeform comments to provide more context about their answer through the little speech bubble icon.
One of my goals this year is to make this feature quicker to use for common types of feedback,
and to facilitate quantitative analysis of the responses (to some degree).</p>
<p>At the end of the survey, respondents even get a knowledge score based on their answers,
which provides immediate value and motivation which reduces survey fatigue.</p>
<p><dfn id="part2">Part 2</dfn> is more freeform, and usually includes multiple-choice questions about tools and resources, freeform questions about pain points, and of course, demographics.</p>
<p>One of the novel things I tried in the 2022 State of CSS survey was to <a href="https://lea.verou.me/blog/2022/07/help-design-the-state-of-css-survey-2022/">involve the community in the design process</a>,
with one-click voting for the features to ask about.
These were actually GitHub Issues with certain labels.
Two years prior I had released <a href="https://lea.verou.me/blog/2020/07/releasing-mavoice-a-free-app-to-vote-on-repo-issues/">MaVoice: an app to facilitate one click voting on Issues in <em>any</em> repo</a>,
and it <a href="https://projects.verou.me/mavoice/?repo=devographics/surveys&labels=State%20of%20CSS%202022">fit the bill perfectly here</a>.</p>
<p>This process worked exceptionally well for uncovering blind spots: it turned out there were a bunch of CSS features that would be good to ask about, but were simply not on our radar.
This is one of the reasons I strongly believe in transparency and co-design: <strong>no one human or small team can ever match the collective intelligence of the community</strong>.</p>
<p>Predictably, I plan to try the same approach for State of HTML.
Instead of using MaVoice, this year I’m trying <a href="https://github.com/features/discussions">GitHub Discussions</a>.
These allow one click voting from the GitHub interface itself,
without users having to authorize a separate app.
They also allow for more discussion, and do not clutter Issues, which are better suited for – well – actual issues.</p>
<p>I have created a Discussions category for this and seeded it with 55 features spanning 12 focus areas (Forms & Editing, Making Web Components, Consuming Web Components, ARIA & Accessibility APIs, Embedding, Multimedia, Interactivity, Semantic HTML, Templating, Bridging the gap with native, Performance, Security & Privacy).
These initial ideas and focus areas came from a combination of personal research, as well as several brainstorming sessions with the <a href="https://www.w3.org/community/webdx/">WebDX CG</a>.</p>
<p><a class="call-to-action" href="https://github.com/Devographics/surveys/discussions/categories/state-of-html-2023-features">Vote on Features for State of HTML 2023!</a></p>
<p><img src="https://lea.verou.me/blog/2023/design-state-of-html/images/discussions.png" alt="Screenshot of list of discussions" /></p>
<p>You can also see a (read-only) summary of the proposed features with their metadata <a href="https://coda.io/@leaverou/html-features">here</a>
though keep in mind that it’s manually updated so it may not not include new proposals.</p>
<p>If you can think of features we missed, please <a href="https://github.com/Devographics/surveys/discussions/new?category=state-of-html-2023-features">post a new Discussion in this category</a>.
There is also a more general <a href="https://github.com/Devographics/surveys/discussions/categories/state-of-html-2023-design">💬 State of HTML 2023 Design</a> category,
for meta-discussions on Part 1 of the survey, and design brainstorming on Part 2.</p>
<div class="note">
<p>Note that <strong>the feedback period will be open for two weeks, until August 10th</strong>.
After that point, feedback may still be taken into account, but it may be too late in the process to make a difference.</p>
</div>
<p>Some things to keep in mind when voting and generally participating in these discussions:</p>
<ul>
<li>The votes and proposals collected through this process are <strong>only one of the many variables</strong> that feed into deciding what to ask about, and are <strong>non-binding</strong>.</li>
<li>There are <strong>two goals</strong> to balance here:
<ol>
<li>The survey needs to provide value to developers – and be fun to fill in!</li>
<li>The survey needs to provide value to browsers, i.e. get them actionable feedback they can use to help prioritize what to work on. This is the main way that these surveys have impact on the web platform, and is at least as important as (1).</li>
</ol>
</li>
<li>While the title is “State of HTML”, certain JS APIs or even CSS syntax is also relevant, especially those very close to HTML, such as DOM, ARIA, Web Components, PWAs etc.</li>
<li>Stable features that have existed for a long time and are widely known are generally less likely to make it to the survey.</li>
</ul>
<p><a class="call-to-action" href="https://github.com/Devographics/surveys/discussions/categories/state-of-html-2023-features">Now go vote! 🗳</a></p>
Numbers or Brackets for numeric questions?2023-08-03T00:00:00Zhttps://lea.verou.me/blog/2023/numbers-vs-brackets/<p>As you may know, this summer I am leading the design of the inaugural <a href="https://lea.verou.me/blog/2023/design-state-of-html/">State of HTML</a> survey.
Naturally, I am also exploring ways to improve both survey UX, as well as all questions.</p>
<p><em>Shaine Madala</em>, a data scientist working on the survey design team <a href="https://github.com/Devographics/surveys/discussions/166#discussioncomment-6625819">proposed</a> using numerical inputs instead of brackets for the income question.
While <a href="https://github.com/Devographics/surveys/discussions/166#discussioncomment-6626156">I was initially against it</a>,
I decided to explore this a bit further, which changed my opinion.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/numbers-vs-brackets/images/income-brackets.png" alt="Multiple choice question showing income brackets starting from $0-$10 up to "More than $200k"" /></p>
<figcaption>The current income question, which uses 6 brackets plus a "Not Applicable" option.</figcaption>
</figure>
<p>There are actually four demographics questions in <em>State of X</em> surveys where the answer is essentially a number, yet we ask respondents to select a bracket:
age, years of experience, company size, and income.</p>
<p>The arguments for brackets are:</p>
<ol>
<li>They are more privacy preserving for sensitive questions (e.g. people may feel more comfortable sharing an income bracket than their actual income)</li>
<li>They are more efficient to input (one click vs homing to keyboard and hitting several keystrokes).</li>
<li>In some cases respondents may not know the precise number offhand (e.g. company size)</li>
</ol>
<p>The arguments for numerical input are:</p>
<ol>
<li>Depending on the specifics, these can actually be faster to answer overall since they involve lower cognitive overhead (for known numbers).</li>
<li>The brackets are applied at the analysis stage, so they can be designed to provide a better overview of the dataset</li>
<li>More elaborate statistics can be computed (e.g. averages, medians, stdevs, the sky is the limit)</li>
</ol>
<h2 id="which-one-is-faster%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#which-one-is-faster%3F">Which one is faster?</a></h2>
<p>We can actually calculate this!
Average reading speed for non-fiction is around 240 wpm (= 250ms/word) <sup class="footnote-ref"><a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fn1" id="fnref1">[1]</a></sup>
Therefore, we can approximate reading time for each question by multiplying number of brackets × average words per bracket (wpb) × 250ms.</p>
<p>However, this assumes the respondent reads all brackets from top to bottom, but this is a rare worst case scenario.
Usually they stop reading once they find the bracket that matches their answer, and they may even skip some brackets, performing a sort of manual binary search.
We should probably <strong>halve these times</strong> to get a more realistic estimate.</p>
<p>Average typing speed is 200 cpm <sup class="footnote-ref"><a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fn2" id="fnref2">[2]</a></sup> (≈ 300ms/character).
This means we can approximate typing time for each question by multiplying the number of digits on average × 300ms.</p>
<p>Let’s see how this works out for each question:</p>
<table>
<thead>
<tr>
<th>Question</th>
<th>Brackets</th>
<th>WPB</th>
<th>Reading time</th>
<th>Avg Digits</th>
<th>Typing time</th>
</tr>
</thead>
<tbody>
<tr>
<td>Age</td>
<td>8</td>
<td>4</td>
<td>4s</td>
<td>2</td>
<td>0.6s</td>
</tr>
<tr>
<td>Years of Experience</td>
<td>6</td>
<td>4</td>
<td>3s</td>
<td>2</td>
<td>0.6s</td>
</tr>
<tr>
<td>Company size</td>
<td>9</td>
<td>4</td>
<td>4.5s</td>
<td>3</td>
<td>0.9s</td>
</tr>
<tr>
<td>Income</td>
<td>7</td>
<td>2</td>
<td>1.75s</td>
<td>5</td>
<td>1.5s</td>
</tr>
</tbody>
</table>
<p>As you can see, despite our initial intuition that brackets are faster, the time it takes to read each bracketed question <strong>vastly</strong> outweighs typing time for all questions!</p>
<p>Of course, this is a simplification.
There are models in <a href="https://en.wikipedia.org/wiki/Human%E2%80%93computer_interaction">HCI</a>, such as <a href="https://en.wikipedia.org/wiki/Keystroke-level_model">KLM</a> that can more accurately estimate the time it takes for certain UI flows.
We even taught some of these to MIT students in <a href="http://web.mit.edu/6.813/www/sp18/classes/10-more-efficiency/#keystroke_level_model">6.813</a>,
as well as <a href="https://designftw.mit.edu/">its successor</a>.</p>
<p>For example, here are some of the variables we left out in our analysis above:</p>
<ul>
<li>When answering with numerical input, most users need to home from mouse to keyboard, which takes time (estimated as 0.4s in KLM)
and then focus the input so they can write in it, which takes an additional click (estimated as 0.2s in KLM)</li>
<li>When answering with brackets, users need to move the mouse to the correct bracket, which takes time (KLM estimates all pointing tasks as a flat 1.1s, but this can be more accurately estimated using <a href="https://en.wikipedia.org/wiki/Fitts%27s_law">Fitts’ Law</a>)</li>
<li>We are assuming that the decision is instantaneous, but doing the mental math of comparing the number in your head to the bracket numbers also takes time.</li>
</ul>
<p>However, given the vast difference in times, I don’t think a more accurate model would change the conclusion much.</p>
<div class="note">
<p>Note that this analysis is based on a desktop interface, primarily because it’s easier (most of these models were developed before mobile was widespread, e.g. KLM was invented in 1978!)
Mobile would require a separate calculation taking into account the specifics of mobile interaction (e.g. the time it takes for the keyboard to pop up), though the same logic applies.
(thanks Tim for <a href="https://twitter.com/gumnos/status/1687199431819763712">this exellent question</a>!)</p>
</div>
<h2 id="what-about-sliders%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#what-about-sliders%3F">What about sliders?</a></h2>
<p>Sliders are uncommon in surveys, and for good reason.
They offer the most benefit in UIs where changes to the value provide <strong>feedback</strong>, and allow users to <strong>iteratively approach the desired value by reacting to this feedback</strong>.
For example:</p>
<ul>
<li>In a color picker, the user can zero in to the desired coordinates iteratively, by seeing the color change in real time</li>
<li>In a video player, the user can drag the slider to the right time by getting feedback about video frames.</li>
<li>In searches (e.g. for flights), dragging the slider updates the results in real time, allowing the user to gradually refine their search with suitable tradeoffs</li>
</ul>
<p>In surveys, there is usually no feedback, which eliminates this core benefit.</p>
<p>When the number is known in advance, sliders are usually a poor choice, except when we have very few numbers to choose among (e.g. a 1-5 rating)
and the slider UI makes it very clear where to click to select each of them, or we don’t much care about the number we select (e.g. search flights by departure time).<sup class="footnote-ref"><a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fn3" id="fnref3">[3]</a></sup>
None of our demographics questions falls in this category (unless bracketed, in which case why not use regular brackets?).</p>
<p>There are several reasons for this:</p>
<ul>
<li>It is hard to predict where exactly to click to select the desired number. The denser the range, the harder it is.</li>
<li>Even if you know where to click, it’s <a href="https://www.nngroup.com/articles/gui-slider-controls/#:~:text=Imprecise%20Interactions">hard to do so on mobile</a></li>
<li>Dragging a slider on desktop is generally slower than typing the number outright.<sup class="footnote-ref"><a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fn4" id="fnref4">[4]</a></sup></li>
</ul>
<h2 id="%3Cinput-type%3Dnumber%3E-all-the-things%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#%3Cinput-type%3Dnumber%3E-all-the-things%3F"><code><input type=number></code> all the things?</a></h2>
<p>Efficiency is not the only consideration here.
Privacy is a big one. These surveys are anonoymous, but respondents are still often concerned about entering data they consider sensitive.
Also, for the efficiency argument to hold true, the numerical answer needs to be top of mind, which is not always the case.</p>
<p>I summarize my recommendations below.</p>
<h3 id="age" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#age">Age</a></h3>
<p>This is a two digit number, that is always top of mind. <strong>Number input.</strong></p>
<h3 id="years-of-experience" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#years-of-experience">Years of experience</a></h3>
<p>This is a 1-2 digit number, and it is either top of mind, or very close to it. <strong>Number input.</strong></p>
<h3 id="company-size" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#company-size">Company size</a></h3>
<p>While most people know their rough company size, they very rarely would be able to provide an exact number without searching.
This is a good candidate for <strong>brackets</strong>.
However, the number of brackets should be reduced from the current 9 (does the difference between 2-5 and 6-10 employees really matter?),
and their labels should be copyedited for scannability.</p>
<p>We should also take existing data into account.
Looking at the <a href="https://2022.stateofcss.com/en-US/demographics/#company_size">State of CSS 2022 results for this question</a>,
it appears that about one third of respondents work at companies with 2-100 people,
so we should probably not combine these 5 brackets into one, like I was planning to propose.
101 to 1000 employees is also the existing bracket with the most responses (15.1%), so we could narrow it a little,
shifting some of its respondents to the previous bracket.</p>
<p><img src="https://lea.verou.me/blog/2023/numbers-vs-brackets/images/company-size-results.png" alt="Chart of responses for company size" /></p>
<p>Taking all these factors into consideration,
I proposed the following brackets:</p>
<ul>
<li>Just me!</li>
<li>Small (2 - 50)</li>
<li>Medium (51 - 200)</li>
<li>Large (201 - 1000)</li>
<li>Very Large (1000+)</li>
</ul>
<h3 id="income" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#income">Income</a></h3>
<p>The question that started it all is unfortunately the hardest.</p>
<p>Income is a number that people know (or can approximate).
It is faster to type, but only marginally (1.75s vs 1.5s).
We can however reduce the keystrokes further (from 1.5s to 0.6s on average) by asking people to enter thousands.</p>
<p>The biggest concern here is <strong>privacy</strong>.
Would people be comfortable sharing a more precise number?
We could mitigate this somewhat by explicitly instructing respondents to round it further, e.g. to the nearest multiple of 10:</p>
<blockquote class="question">
<strong>What is your approximate yearly income (before taxes)?</strong>
<small>
Feel free to round to the nearest multiple of 10 if you are not comfortable sharing an exact number.
If it varies by year, please enter an average.
</small>
<br />
<label>$<input type="number" size="3" /> ,000</label>
</blockquote>
<p>However, this assumes that the privacy issues are about granularity, or about the number being too low (rounding to 10s could help with both).
However, <a href="https://people.csail.mit.edu/karger/">David Karger</a> made an excellent point in <a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#comment-6246846722">the comments</a>,
that people at the higher income brackets may also be reluctant to share their income:</p>
<blockquote>
<p>I don’t think that rounding off accomplishes anything. It’s not the least significant digit that people care about, but the most significant digit. This all depends on who they imagine will read the data of course. But consider some techy earnings, say 350k. That’s quite a generous salary and some people might be embarrassed to reveal their good fortune. Rounding it to 300k would still be embarrassing. On the other hand, a bracket like 150 to 500 would give them wiggle room to say that they’re earning a decent salary without revealing that they’re earning a fantastic one. I don’t have immediate insight into what brackets should be chosen to give people the cover they need to be truthful, but I think they will work better for this question.</p>
</blockquote>
<p>Another idea was to offer UI that lets users indicate that the number they have entered is actually an upper or lower bound.</p>
<blockquote class="question">
<strong>What is your approximate yearly income (before taxes)?</strong>
<select>
<option selected="">About</option>
<option>Over</option>
<option>Under</option>
<option>Exactly</option>
</select>
<label>$<input type="number" size="3" /> ,000</label>
</blockquote>
<p>Of course, a dropdown PLUS a number input is much slower than using brackets,
but if only a tiny fraction of respondents uses it, it does not affect the analysis of the average case.</p>
<p>However, after careful consideration and input, both <a href="https://github.com/Devographics/surveys/discussions/177#discussioncomment-6634104">qualitative</a> and <a href="https://twitter.com/SachaGreif/status/1688447006459805696">quantitative</a>, it appears that privacy is a much bigger factor than I had previously realized.
Even though I was aware that people see income level as sensitive data (more so in certain cultures than others),
I had not fully realized the extent of this.
In the end, I think <strong>the additional privacy afforded by brackets far outweighs any argument for efficiency or data analysis convenience</strong>.</p>
<h2 id="conclusion" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#conclusion">Conclusion</a></h2>
<p>I’m sure there is a lot of prior art on the general dilemma on numerical inputs vs brackets,
but I wanted to do some analysis with the specifics of this case and outline an analytical framework for answering these kinds of dilemmas.</p>
<p>That said, if you know of any relevant prior art, please share it in the comments!
Same if you can spot any flaws in my analysis or recommendations.</p>
<p>You could also <a href="https://github.com/Devographics/surveys/discussions/177">check out the relevant discussion</a> as there may be good points there.</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p><a href="https://www.sciencedirect.com/science/article/abs/pii/S0749596X19300786">https://www.sciencedirect.com/science/article/abs/pii/S0749596X19300786</a> <a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p><a href="https://www.typingpal.com/en/blog/good-typing-speed">https://www.typingpal.com/en/blog/good-typing-speed</a> <a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p><a href="https://www.nngroup.com/articles/gui-slider-controls/">Slider Design: Rules of Thumb, NNGroup, 2015</a> <a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fnref3" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn4" class="footnote-item"><p>KLM is a poor model for dragging tasks for two reasons:
First, it regards dragging as simply a combination of three actions: <em>button press, mouse move, button release</em>.
But we all know from experience that dragging is much harder than simply pointing, as managing two tasks simultaneously (holding down the mouse button and moving the pointer) is almost always harder than doing them sequentially.
Second, it assumes that all pointing tasks have a fixed cost (1.1s), which may be acceptable for actual pointing tasks, but the inaccuracy is magnified for dragging tasks.
A lot of HCI literature (and even <a href="https://www.nngroup.com/articles/gui-slider-controls/#:~:text=subjected%20to%20the-,steering%20law,-%2C%20which%20describes%20the">NNGroup</a>) refers to the <a href="https://en.wikipedia.org/wiki/Steering_law">Steering Law</a> to estimate the time it takes to use a slider,
however modern sliders (and scrollbars) do not require steering, as they are not constrained to a single axis:
once dragging is initiated, moving the pointer in any direction adjusts the slider, until the mouse button is released.
<a href="https://en.wikipedia.org/wiki/Fitts%27s_law">Fitts Law</a> actually appears to be a better model here, and indeed there are <a href="https://scholar.google.com/scholar?hl=en&as_sdt=0%2C22&q=fitts+law+dragging">many papers</a> extending it to dragging.
However, evaluating this research is out of scope for this post. <a href="https://lea.verou.me/blog/2023/numbers-vs-brackets/#fnref4" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
State of HTML 2023 now open!2023-09-22T00:00:00Zhttps://lea.verou.me/blog/2023/state-of-html-2023/<p><strong>tl;dr</strong> the brand new State of HTML survey is finally open!</p>
<p><a href="https://survey.devographics.com/en-US/survey/state-of-html/2023/?source=leaverou" class="call-to-action" target="_blank">Take State of HTML 2023 Survey</a></p>
<p>Benefits to you:</p>
<ul>
<li>Survey results are <strong>used by browsers to prioritize roadmaps</strong> — the reason Google is funding this.
Time spent thoughtfully filling them out is an investment that can come back to you tenfold
in the form of seeing features you care about implemented, browser incompatibilities being prioritized, and gaps in the platform being addressed.</li>
<li>In addition to browsers, several standards groups are also using the results for prioritization and decision-making.</li>
<li>Learn about new and upcoming features you may have missed; add features to your reading list and get a list of resources at the end!</li>
<li>Get a personalized score and see how you compare to other respondents</li>
<li>Learn about the latest trends in the ecosystem and what other developers are focusing on</li>
</ul>
<div class="info">
<p>While the survey will be open for 3 weeks, responses entered <strong>within the first 9 days (until October 1st)</strong> will have a much higher impact on the Web,
as preliminary data will be used to inform <a href="https://web.dev/interop-2024-proposals/">Interop 2024</a> proposals.</p>
</div>
<figure>
<p><img src="https://lea.verou.me/blog/2023/state-of-html-2023/images/logo.png" alt="State of HTML 2023 Logo" /></p>
<figcaption>
<p>The State of HTML logo, designed by <a href="https://chriskirknielsen.com/">Chris Kirk-Nielsen</a>, who I think surpassed himself with this one!</p>
</figcaption>
</figure>
<h2 id="background" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#background">Background</a></h2>
<p>This is likely the most ambitious <a href="https://www.devographics.com/">Devographics</a> survey to date.
For <a href="https://lea.verou.me/blog/2023/design-state-of-html/">the past couple of months</a>, I’ve been hard at work leading a small product team spread across three continents (2am to 8am became my second work shift 😅).
We embarked on this mission with some uncertainty about whether there were <em>enough</em> features for a State of HTML survey,
but quickly found ourselves with the opposite problem:
there were too many, all with good reasons for inclusion!
To help weigh the tradeoffs and decide what makes the cut we consulted both <a href="https://github.com/Devographics/surveys/discussions/categories/state-of-html-2023-features?discussions_q=is%3Aopen+category%3A%22State+of+HTML+2023+Features%22+sort%3Atop">the developer community</a>,
as well as stakeholders across browsers, standards groups, community groups, and more.</p>
<p>We even designed new UI controls to facilitate collecting the types of complex data that were needed without making the questions too taxing,
and did original UX research to validate them.
Once the dust settles, I plan to write separate blog posts about some of these.</p>
<h2 id="faq" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#faq">FAQ</a></h2>
<h3 id="can-i-edit-my-responses%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#can-i-edit-my-responses%3F">Can I edit my responses?</a></h3>
<p><strong>Absolutely!</strong> Do not worry about filling it out perfectly in one go.
If you create an account, you can edit your responses for the whole period the survey is open, and even split filling it out across multiple devices (e.g. start on your phone, then fill out some on your desktop, etc.)
Even if you’re filling it out anonymously, you can still edit responses on your device for a while.
You could even start anonymously and create an account later, and your responses will be preserved (the only issue is filling it out anonymously, then logging in with an existing account).</p>
<p>So, perhaps the call to action above should be…</p>
<p><a href="https://survey.devographics.com/en-US/survey/state-of-html/2023/?source=leaverou" class="call-to-action" target="_blank"><em>Start</em> State of HTML 2023 Survey</a></p>
<h3 id="why-are-there-js-questions-in-an-html-survey%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#why-are-there-js-questions-in-an-html-survey%3F">Why are there JS questions in an HTML survey?</a></h3>
<p>For the same reason there are JS APIs in the <a href="https://html.spec.whatwg.org/">HTML standard</a>:
many JS APIs are intrinsically related to HTML.
We mainly included JS APIs in the following areas:</p>
<ul>
<li>APIs used to manipulate HTML dynamically (DOM, form validation, etc.)</li>
<li>Web Components APIs, used to create custom HTML elements</li>
<li>APIs used to create web apps that feel like native apps (e.g. Service Workers, Web App Manifest, etc.)</li>
</ul>
<p>If you don’t write any JS, we absolutely still want to hear from you!
In fact, I would encourage you <strong>even more strongly</strong> to fill out the survey: we need to hear from folks who don’t write JS, as they are often underrepresented.
Please feel free to skip any JS-related questions (all questions are optional anyway) or select that you have never heard these features.
There is a question at the end, where you can select that you only write HTML/CSS:</p>
<p><img src="https://lea.verou.me/blog/2023/state-of-html-2023/images/html-js-balance.png" alt="Question about HTML/CSS and JS balance" /></p>
<h3 id="is-the-survey-only-available-in-english%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#is-the-survey-only-available-in-english%3F">Is the survey only available in English?</a></h3>
<p>Absolutely not! Localization has been an integral part of these surveys since the beginning.
Fun fact: Nobody in the core <a href="https://lea.verou.me/blog/2023/state-of-html-2023/#acknowledgements">State of HTML team</a> is a native English speaker.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/state-of-html-2023/images/languages.png" alt="Screenshot showing dozens of languages" /></p>
<figcaption>
<p>Each survey gets (at least partially) translated to over 30 languages.</p>
</figcaption>
</figure>
<p>However, since translations are a community effort, they are not necessarily complete, especially in the beginning.
If you are a native speaker of a language that is not yet complete, please consider <a href="https://github.com/Devographics/locale-en-US">helping out</a>!</p>
<h3 id="what-does-my-score-mean%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#what-does-my-score-mean%3F">What does my score mean?</a></h3>
<p>Previous surveys reported score as a percentage: “You have heard or used X out of Y features mentioned in the survey”.
This one did too at first:</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/state-of-html-2023/images/my-score-old.png" alt="80% score, 105/131 heard or used" /></p>
<figcaption>
<p>This was my own score when the survey first launched, and I <em>created</em> the darn survey 😅
Our engineer, <a href="https://sachagreif.com/">Sacha</a> who is also the founder of <a href="https://www.devographics.com/">Devographics</a> got 19%!</p>
</figcaption>
</figure>
<p>These were a lot lower for this survey, for two reasons:</p>
<ol>
<li>It asks about <em>a lot</em> of cutting edge features, more than the other surveys.
As I mentioned above, we had a lot of difficult tradeoffs to make,
and had to cut a ton of features that were otherwise a great fit.
We err’ed on the side of more cutting edge features, as those are the areas the survey can help make the most difference in the ecosystem.</li>
<li>To save on space, and be able to ask about more features, we used a new compact format for some of the more stable features, which only asks about usage, not awareness.
Here is an example from the first section of the survey (Forms):
<img src="https://lea.verou.me/blog/2023/state-of-html-2023/images/form-validation.png" alt="Form validation question screenshot" />
However, this means that if you have never used a feature, it does not count towards your score, even if you have been aware of it for years.
It therefore felt unfair to many to report that you’ve “heard or used” X% of features, when there was no way to express that you have heard 89 out of 131 of them!</li>
</ol>
<p>To address this, we changed the score to be a sum of points, a bit like a video game:
each used feature is worth 10 points, each known feature is worth 5 points.</p>
<p>Since the new score is harder to interpret by itself and only makes sense in comparison to others,
we also show your rank among other participants, to make this easier.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/state-of-html-2023/images/my-score-new.png" alt="920 pts score, used 79 features out of 131, heard of 26 more" /></p>
<figcaption>
<p>My score after the change.
If you have already taken the survey, you can just revisit it (with the same device & browser if filled it in anonymously)
and go straight to the finish page to see your new score and ranking!</p>
</figcaption>
</figure>
<h3 id="i-found-a-bug%2C-what-should-i-do%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#i-found-a-bug%2C-what-should-i-do%3F">I found a bug, what should I do?</a></h3>
<p>Please file an issue so we can fix it!</p>
<ul>
<li><a href="https://github.com/Devographics/surveys/issues/new">File content issue</a></li>
<li><a href="https://github.com/Devographics/Monorepo/issues/new">File technical issue</a></li>
</ul>
<h2 id="acknowledgements" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#acknowledgements">Acknowledgements</a></h2>
<p>This survey would not have been possible without the hard work of many people.
Besides myself (Lea Verou), this includes the rest of the team:</p>
<ul>
<li>Engineering team: Sacha Greif, Eric Burel</li>
<li>UX research & data science team: Shaine Rosewel Matala, Michael Quiapos, Gio Vernell Quiogue</li>
<li>Our logo designer, <a href="https://chriskirknielsen.com/">Chris Kirk-Nielsen</a></li>
</ul>
<p>And several volunteers:</p>
<ul>
<li><a href="https://tink.uk/">Léonie Watson</a> for accessibility feedback</li>
<li>Our usability testing participants</li>
<li>…and all folks who provided early feedback throuhgout the process</li>
</ul>
<p>Last but not least, <strong>Kadir Topal</strong> made the survey possible in the first place, by proposing it and securing funding from Google.</p>
<p>Thank you all! 🙏🏼</p>
<h2 id="press-coverage-(selected)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/state-of-html-2023/#press-coverage-(selected)">Press coverage (selected)</a></h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=sPWlakxKRm8">Turns out I know less about HTML than I thought! 😅 - Kevin Powell</a> (Video)</li>
<li><a href="https://dev.to/dianale/are-you-an-html-expert-find-out-with-the-new-state-of-html-2023-survey-58g4">Are you an HTML expert? Find out with the new State of HTML 2023 survey - dev.to</a></li>
<li><a href="https://blog.codepen.io/2023/10/02/chris-corner-things-i-totally-didnt-know-about-that-i-learned-from-taking-the-state-of-html-2023-survey/">Chris’ Corner: Things I Totally Didn’t Know About That I Learned From Taking the State of HTML 2023 Survey</a></li>
<li><a href="https://frontendfoc.us/issues/611">Frontend Focus</a></li>
<li><a href="https://css-weekly.com/issue-562/">CSS Weekly</a></li>
<li><a href="https://thathtml.blog/2023/09/the-state-of-html/">The HTML Blog</a></li>
</ul>
<p><a href="https://survey.devographics.com/en-US/survey/state-of-html/2023/?source=leaverou" class="call-to-action" target="_blank">You still haven’t started the State of HTML 2023 survey?!</a></p>
Minimalist Affordances: Making the right tradeoffs2023-11-02T00:00:00Zhttps://lea.verou.me/blog/2023/minimalist-affordances/<p>Usability and aesthetics <em>usually</em> go hand in hand.
In fact, there is even what we call the <a href="https://www.nngroup.com/articles/aesthetic-usability-effect/"><em>“Aesthetic Usability Effect”</em></a>:
users perceive beautiful interfaces as easier to use and cut them more slack when it comes to minor usabiity issues.</p>
<p>Unfortunately, sometimes usability and aesthetics can be at odds, also known as “form over function”.</p>
<h2 id="simplicity%2C-and-knowing-when-to-stop" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/minimalist-affordances/#simplicity%2C-and-knowing-when-to-stop">Simplicity, and knowing when to stop</a></h2>
<p>A common incarnation of form-over-function, is when designers start identifying signifiers and affordances as noise to be eliminated,
sacrificing a great deal of learnability for an — often marginal — improvement in aesthetics.</p>
<p><a href="https://www.nngroup.com/articles/aesthetic-minimalist-design/">Aesthetic and Minimalist Design</a> is one of the Nielsen/Norman core usability heuristics (and all other heuristics taxonomies have something similar).
More poetically, Antoine de Saint-Exupéry said <em>“Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away”</em>.
However, this is one of those cases where everyone agrees with the theory, but the devil is in the details (though user testing can do wonders for consensus).</p>
<p>Case in point: The new Github comment UI is <em>beautiful</em>.
Look at how the text area smoothly blends with the tab, creating an irregular and visually interesting shape!</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/new-comment.png" alt="Screenshot" /></p>
<figcaption>
<p>The new GitHub commenting UI, unfocused.</p>
</figcaption>
</figure>
<figure>
<p><img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/new-comment-focused.png" alt="Screenshot" /></p>
<figcaption>
<p>The new GitHub commenting UI, focused. Am I the only one that expected the focus outline to also follow the irregular shape?</p>
</figcaption>
</figure>
<p>However, I cannot for the life of me internalize that this is a text field that I can type in.
Even after using it over a dozen times, I still have to do a double take every time (<em>“Where is the comment field?!”</em>, <em>“Why is this read-only?”</em>).</p>
<p>For comparison, this was the old UI:</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/old-comment.png" alt="Screenshot" />
<img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/old-comment-focused.png" alt="Screenshot" /></p>
<figcaption>
The old GitHub commenting UI, with and without focus.
</figcaption>
</figure>
<p>While definitely more cluttered, its main UI elements were much more recognizable:
there is a text field, indicated by the rounded rectangle, and tabs, indicated by the light gray border around the active tab.
By merging the two, both affordances are watered down to the point of being unrecognizable.</p>
<p>Yes, there was more visual clutter, not all of which serves a purpose.
A skilled designer could probably eliminate the rounded rectangle around the entire area without impacting usability.
But the current design goes too far, and throws the baby out with the bathwater.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/twitter.png" alt="Screenshot of Twitter’s new tweet modal" /></p>
<figcaption>
<p>Twitter seems to be trying something similar, but since there is no irregular shape,
the text field affordance is not entirely lost.</p>
</figcaption>
</figure>
<h2 id="the-ever-evolving-vocabulary-of-user-interaction" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/minimalist-affordances/#the-ever-evolving-vocabulary-of-user-interaction">The ever-evolving vocabulary of user interaction</a></h2>
<p>Communication is all about mutually understood conventions: <a href="https://www.babbel.com/en/magazine/english-errors-that-are-now-accepted-as-correct">a sufficiently widespread grammatical mistake eventually becomes part of the language</a>.
In the language of user interfaces, affordances and signifiers are the vocabulary, and the same principles apply.
Learnability is not an intrinsic property of a UI; it is a function of the context (cultural and otherwise) in which it is used.</p>
<p>Many affordances and signifiers use metaphors from the physical world to communicate what a user can do.
For example a button that looks raised reminds us of physical buttons.
Tabs are a metaphor for the tabs in a binder.
Others are entirely arbitrary and acquire meaning through learning, such as link underlines or the “hamburger” menu icon.</p>
<p>We see the same pattern in language: some words are onomatopoeic, such as “buzz” or “meow”, while others are entirely learned, such as “dog” or “cat”.
Similarly, writing systems began as pictograms, but evolved to be more abstract and symbolic.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/alphabet-evolution.png" alt="Egyptian pictograms to Greek letters" /></p>
<figcaption>
<p>Insight and picture from <a href="https://ux.stackexchange.com/a/56896/11761">https://ux.stackexchange.com/a/56896/11761</a></p>
<blockquote>
<p>At first, the symbols are direct (if cartoony) representations. Then they slowly lose their extrinsic meaning and become defined more by our conventions of using them (our shared language) and the references to outside concepts disappear.</p>
</blockquote>
<p>It’s worth reading the whole post if you have time.</p>
</figcaption>
</figure>
<p>UI evolution is rife with patterns that began as obscure and ended up as obvious.
In other words, <a href="https://www.nngroup.com/articles/consistency-and-standards/">external consistency</a> improved,
not because the UIs changed, but because the environment did.</p>
<p>Some examples you are undoubtedly familiar with:</p>
<ul>
<li>Underlines have always been a strong affordance for links (to the point that <a href="https://www.nngroup.com/articles/guidelines-for-visualizing-links/#:~:text=Don%27t%20underline%20any%20text%20that%27s%20not%20a%20link">using them for anything else is an antipattern</a>).
However, users evolved to perceive weaker signals as links, such as different colors, especially <a href="https://www.nngroup.com/articles/guidelines-for-visualizing-links/#:~:text=Never%20show%20text%20in%20your%20chosen%20link%20colors%20unless%20it%27s%20a%20link">if used consistently</a>.</li>
<li>Clicking a website logo to go to the homepage was once an obscure hidden interaction, almost an easter egg.
It is now so conventional that <a href="https://www.nngroup.com/articles/homepage-links/#:~:text=Website%20logos%20should%20link%20to%20the%20homepage">a logo that does nothing when clicked is considered a usability issue</a> (though having separate Home links is <a href="https://www.nngroup.com/articles/homepage-links/">still the recommendation</a>, <a href="https://ux.stackexchange.com/a/81736/11761">28 years after the pattern was introduced</a>!).</li>
<li>Buttons used to need a 3D appearance to be perceived as such.
We gradually evolved such that any rectangle around text is perceived as a button, even if it is entirely flat (though research shows that <a href="https://www.nngroup.com/articles/flat-ui-less-attention-cause-uncertainty/">they</a> <a href="https://www.nngroup.com/articles/clickable-elements/">are still</a> <a href="https://www.nngroup.com/articles/flat-design/">less</a> <a href="https://www.nngroup.com/articles/flat-design-long-exposure/">effective</a>).</li>
</ul>
<p>Could it be that the new GitHub comment UI is the beginning of a new convention?
It’s possible, but the odds are slim.
For new conventions to become established, they need to be widespread.
Links, buttons, website logos are present on any website, so users get plenty of exposure to any evolution in their design.
Similarly, multiline text fields and tabs are very commonplace UI elements. However, <em>their combination</em> is far less common.
Even if every tabbed text field on the Web begun using the exact same design, the average user would still not get enough exposure to internalize it.</p>
<h2 id="ux-stockholm-syndrome" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/minimalist-affordances/#ux-stockholm-syndrome">UX Stockholm Syndrome</a></h2>
<p>It is entirely possible that I’m overestimating the impact of this on GitHub users.
After all, I have not done user testing on it, so I’m basing my opinion on my own experience, and on what I’ve learned about usability spending the better part of the last decade at MIT <a href="https://designftw.mit.edu/">teaching it</a> and doing a PhD on it.</p>
<p>I wondered if it could be an A/B test, so I asked <a href="https://svgees.us/">Chris</a> to show me what UI he was seeing.
He was also seeing the new UI, but interestingly he expressed frustration about being unable to tell where the text field actually is, and where he can type even before I told him about this article.
Whether or not it’s not an A/B test, I’m really hoping that GitHub is collecting enough metrics so they can evaluate the impact of this design on user experience at scale.</p>
<p>As for me, I take comfort in knowing that when there is no alternative, users can eventually adapt to any UI, no matter how poor, so I <em>will</em> at some point get used to it.
Airplane cockpits are the canonical example here, but this is commonly seen in UIs of a lot of enterprise software (though <a href="https://uxmag.com/articles/the-future-of-enterprise-design-is-consumer-grade-ux">the wind of change is blowing straight into the face of enterprise UX</a>).</p>
<aside>
<p>Of course even with heavy training, poor UIs can still lead to disastrous consequences,
such as <a href="https://en.wikipedia.org/wiki/Helios_Airways_Flight_522">plane crashes</a>,
<a href="https://www.nngroup.com/articles/medical-usability/">hospital deaths</a> or <a href="https://features.propublica.org/navy-uss-mccain-crash/navy-installed-touch-screen-steering-ten-sailors-paid-with-their-lives/">military accidents</a>.</p>
</aside>
<p>Our life is rife with examples of poor usability, to the point where if something is easy to use, people are often surprised.
There is even what some of us call “<a href="https://www.ianvoyce.com/rant/usability/2015/09/17/bad-ux-and-stockholm-syndrome.html">UX Stockholm Syndrome</a>”: after very prolonged exposure to a poor interface, users start believing that it is easy to use, and even advocate against improvements.
The <a href="https://en.wikipedia.org/wiki/Curse_of_knowledge">curse of knowledge</a> makes them forget how difficult it was to learn, and the prolonged exposure can even make them efficient at using it.</p>
<p>Take <strong>hex colors</strong> for example. Quick, what color is <code>#7A6652</code>?
Learning to mentally translate between hex color notation and actual visible colors takes years of practice.
Hex notation was never designed for humans; it was designed for machines, as a compact way to represent the 3 bytes of RGB channels of earlier screens.
<strong>Humans do not think of colors as combinations of lights.</strong>
It’s not logical that to make brown you combine some red, a bit less green, and even less blue.
That is neither how we think about color, nor does it relate to any of our real-world color mixing experiences.
There are several color models with a more human-centered design, such as <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSL</a>, <a href="https://lea.verou.me/blog/2020/04/lch-colors-in-css-what-why-and-how">LCH</a>, <a href="https://www.smashingmagazine.com/2023/08/oklch-color-spaces-gamuts-css/">OKLCH</a>.
Their coordinates are designed around how humans describe colors,
such as hue for the main color (e.g. red, yellow, green, etc.), chroma/saturation to specify how intense the color is (e.g. 0 would be gray), and lightness to specify how light it is (e.g. white would be 100% and black would be 0%).
Yet, it’s common to see the kinds of people who have had very prolonged exposure to this notation (e.g. web designers) not only prefer it, but even try to sing its praises!</p>
<p>Another example, entirely outside of software, is music notation.
You’ve likely learned it as a child, so it’s hard to remember what the learning experience was like,
and if you regularly read music sheets, you may even believe it’s easy.
But if we try to step back and examine it objectively, it’s highly unintuitive.</p>
<p>Expanding on this would take a whole other article, but I will just give one example.
Take a look at the symbols for notes and pauses:</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/notes.png" alt="Music notes and rests" /></p>
<figcaption>
<p>Image courtesy of <a href="https://www.musicnotes.com/blog/how-to-read-sheet-music/">Musicnotes</a></p>
</figcaption>
</figure>
<p>There is not only an ordering here, but successive symbols even have a fixed ratio of 2.
Yet <em>absolutely nothing</em> in their representation signifies this.
<strong>Nothing in the depiction of ♩ indicates that it is longer than ♪, let alone that it is <em>double</em> the length.</strong>
You just have to learn it.
Heck, there’s nothing even indicating whether a symbol produces sound or not!
Demanding a lot of <a href="https://medium.com/@matthewraychiang/doet-knowledge-in-the-head-and-in-the-world-64f901627eb3">knowledge in the head</a>
is not a problem in itself; it’s a common tradeoff when efficiency is higher priority than learnability.
E.g. the alphabet is also a set of arbitrary symbols we need to learn to be able to form words.
But even the best tradeoff is worse than none, aka having your cake and eating it too beats both options.
Was a tradeoff really necessary here?
Was there really no possible depiction of these symbols that could communicate their purpose, order, and ratios? Or at least a notation that was <a href="https://medium.com/@matthewraychiang/doet-knowledge-in-the-head-and-in-the-world-64f901627eb3">memorable by association rather than straight memorization</a>?</p>
<h2 id="update%3A-github%E2%80%99s-response-(nov-20th%2C-2023)" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/minimalist-affordances/#update%3A-github%E2%80%99s-response-(nov-20th%2C-2023)">Update: GitHub’s response (Nov 20th, 2023)</a></h2>
<p>This post resonated a lot with people on social media.
Here are some selected responses:</p>
<details>
<summary>Selected Social Media Replies</summary>
<p><a href="https://twitter.com/jitl/status/1720272221149581493">https://twitter.com/jitl/status/1720272221149581493</a></p>
<p><a href="https://twitter.com/noeldevelops/status/1724509073964487056">https://twitter.com/noeldevelops/status/1724509073964487056</a></p>
<p><a href="https://twitter.com/zisiszikos/status/1720157900620939519">https://twitter.com/zisiszikos/status/1720157900620939519</a></p>
<p><a href="https://twitter.com/manuelmeister/status/1720147908731818249">https://twitter.com/manuelmeister/status/1720147908731818249</a></p>
<blockquote>
<p>@leaverou @github I really thought the page was broken or incompletely loaded until I saw it enough times to try typing in it. It’s emotionally uncomfortable to type in, fighting how it looks vs. what I know it is.
— <a href="https://front-end.social/@hazula@hachyderm.io">Benjamin @hazula@hachyderm.io</a>, <a href="https://front-end.social/@hazula@hachyderm.io/111345248877999709">Nov 3rd, 2023</a></p>
</blockquote>
</details>
<p>The <a href="https://primer.style/">Primer team at GitHub</a> reached out to me to discuss the issue, and I was happy to see that they were very receptive to feedback.
They then iterated, and came up with a new design that communicates purpose much better, even if less minimalistic:</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/new-comment-fixed.png" alt="" />
<img src="https://lea.verou.me/blog/2023/minimalist-affordances/images/fixed-comment.png" alt="" /></p>
<figcaption>
The textarea is now clearly outlined, both before and after focus, clearly indicating that this is a multiline text field.
</figcaption>
</figure>
<p><a href="https://twitter.com/natalyathree/status/1729161513636884499">https://twitter.com/natalyathree/status/1729161513636884499</a></p>
<blockquote>
<p>@leaverou @github thank you for this post. We have shipped improvements to make it easier again to identify the textarea and distinguish between Write and Preview.
— <a href="https://front-end.social/@dipree@mastodon.social">Daniel Adams (@dipree@mastodon.social)</a>, <a href="https://front-end.social/@dipree@mastodon.social/111444773791934052">Nov 20th, 2023</a></p>
</blockquote>
<p>Always great to see an org that is receptive to feedback!</p>
Eigensolutions: composability as the antidote to overfit2023-12-19T00:00:00Zhttps://lea.verou.me/blog/2023/eigensolutions/<div class="nutshell">
<p><strong>tl;dr:</strong>
<a href="https://bootcamp.uxdesign.cc/overfitting-and-the-problem-with-use-cases-337d9f4bf4d7"><em>Overfitting</em></a> happens when solutions don’t generalize sufficiently and is a hallmark of poor design.
<em>Eigensolutions</em> are the opposite: solutions that generalize so much they expose links between seemingly unrelated use cases.
Designing eigensolutions takes a mindset shift from linear design to <em>composability</em>.</p>
</div>
<h2 id="creator-tools-are-not-uber-or-facebook" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#creator-tools-are-not-uber-or-facebook">Creator tools are not Uber or Facebook</a></h2>
<p>In product literature, the design process looks a bit like this:</p>
<p><img src="https://lea.verou.me/blog/2023/eigensolutions/images/design-linear.png" alt="Pain point -> Use cases -> Ideas -> Solution" /></p>
<p>This works great with the kinds of transactional processes (marketplaces, social media, search engines, etc) most product literature centers around,
but can fall apart when designing creative tools (developer tools, no-code tools, design tools, languages, APIs etc.),
as there are fundamental differences<sup class="footnote-ref"><a href="https://lea.verou.me/blog/2023/eigensolutions/#fn1" id="fnref1">[1]</a></sup> between the two:</p>
<ul>
<li>In <strong>transactional processes</strong>, users have clearly defined goals, and the task is highly specialized
(e.g. <em>“Go to work”</em>, <em>“Order takeout”</em>, <em>“Find accommodation for my upcoming trip”</em>) and can often be modeled as a linear process.</li>
<li>In <strong>creator tools</strong>, use cases vary <em>wildly</em>, goals are neither linear, nor clearly defined, and may even change throughout the session.</li>
</ul>
<p>Creator tools typically ship knowingly addressing only a percentage of their key use cases — otherwise they would never ship at all.
It’s all about balancing UX, use case coverage, and design/implementation effort.</p>
<h3 id="floor-ceiling" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#floor-ceiling">Evaluating user experience: Floor and ceiling</a></h3>
<p>In <a href="https://en.wikipedia.org/wiki/End-user_development">end-user programming</a> we talk about the <em>floor</em> and the <em>ceiling</em> of a tool:</p>
<ul>
<li>The <strong>floor</strong> is the minimum level of knowledge users need to create something useful.</li>
<li>The <strong>ceiling</strong> refers to the extent of what can be created.</li>
</ul>
<aside>
<p><a href="https://mres.medium.com/designing-for-wide-walls-323bdb4e7277">Some</a> people also talk about <a href="https://johnumekubo.com/2017/11/09/low-floors-high-ceilings-and-wide-walls/"><em>wide walls</em></a>: the range of things that can be made (i.e. how domain specific the tool is).</p>
</aside>
<p>I think that vocabulary generalizes more broadly to creator tools, and can be a useful UX metric.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/eigensolutions/images/floor-ceiling.png" alt="A 2x2 chart of various creator tools and where they stand on the floor/ceiling grid" /></p>
<figcaption>
<p><em>Programming languages</em> tend to have high ceiling, but also a high floor: You make anything, but it requires months or years of training,
whereas <em>domain specific GUI builders</em> like Google Forms have a low floor, but also a low ceiling: Anyone can start using them with no training, but you can also only make very specific kinds of things with them.</p>
</figcaption>
</figure>
<p>A product that combines a low floor with a high ceiling is the unicorn of creator tools.
Therefore, most product work in creator tools centers around either <strong>reducing the floor</strong> (making things <em>easier</em>), or <strong>increasing the ceiling</strong> (making things <em>possible</em>).
Which one of the two takes priority depends on various factors (user research, product philosophy, strategy etc.), and could differ by product area or even by feature.</p>
<h3 id="use-case-backlog" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#use-case-backlog">Evaluating use case coverage: The Use Case Backlog</a></h3>
<p>In creator tools, use cases tend to accumulate at a much faster rate than they can be addressed, especially in the beginning.
Therefore we end up with what I call a <em>“use case backlog”</em>: a list of use cases that are within scope, but we cannot yet address
due to lack of resources, good solutions, or both.
The more general purpose and the more ambitious the tool is, the higher the rate of accumulation, since the pool of use cases is naturally larger.</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/eigensolutions/images/use-case-backlog.png" alt="" /></p>
<figcaption>
<p>Pain points get processed into use cases, which accumulate in the use case backlog</p>
</figcaption>
</figure>
<p>Unlike the linear design process of transactional processes,
the design process for creator tools often consists of <em>matching</em> use cases to solutions, which can happen before, during, or after idea conception.</p>
<div class="note">
<p>A product may include <em>both</em> transactional processes and creator tools,
e.g. Instagram is a social media platform (transactional) with a photo editor (creator tool).
Although these tend to be more domain-specific creator tools, which are less good examples for the concepts discussed here.</p>
</div>
<h2 id="eigensolutions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#eigensolutions">From overfitting to eigensolutions</a></h2>
<p><a href="https://shishir.io/">Shishir Mehrotra</a> (of <a href="https://coda.io/">Coda</a>) wrote about the importance of “<a href="https://coda.io/@shishir/eigenquestions-the-art-of-framing-problems"><em>Eigenquestions</em></a>” when framing problems, a term he coined, inspired from his math background:</p>
<blockquote>
<p>the eigenquestion is the question where, if answered, it likely answers the subsequent questions as well.</p>
</blockquote>
<p>This inspired me to name a symmetrical concept I’ve been pondering for a while: <em>Eigensolutions</em>.
<strong>The <em>eigensolution</em> is a solution that addresses several key use cases, that previously appeared unrelated.</strong></p>
<p>An eigensolution is the polar opposite of <a href="https://bootcamp.uxdesign.cc/overfitting-and-the-problem-with-use-cases-337d9f4bf4d7">overfitting</a>.
Overfitting happens when the driving use cases behind a solution are insufficiently diverse,
so the solution ends up being so specific it cannot even generalize to use cases that are clearly related.</p>
<p><strong>Overfitting is one of the worst things that can happen during the design process.</strong>
It is a hallmark of poor design that leads to feature creep and poor user experiences.
It forces product teams to keep adding more features to address the use cases that were not initially addressed.
The result is UI clutter and user confusion, as from the user’s perspective, there are now multiple distinct features that solve subtly different problems.</p>
<h2 id="composability" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#composability">A mindset shift to composability</a></h2>
<p>This is all nice and dandy, but how do we design and ship eigensolutions?
Do we just sit around waiting for inspiration to strike?
Well, we <em>could</em>, but it would be a pretty poor use of resources. :)</p>
<p>Instead, it takes a <em>mindset shift</em>, from the linear <em>Use case → Idea → Solution</em> process to <strong>composability</strong>.
Rather than designing a solution to address only our driving use cases,
step back and ask yourself:
can we design a solution as a <strong>composition</strong> of smaller, more general features, that could be used together to address a broader set of use cases?
In many cases the features required for that composition are already implemented and are just missing one piece: our eigensolution.
In other cases composability may require more than one new feature, but the result can still be a net win since these features are useful on their own and can ship independently.</p>
<p>A composability mindset requires <strong>being aware of pain points and use cases across many different product areas</strong>.
This becomes harder in larger organizations, where product teams are highly specialized.
It’s not impossible, but requires conscious effort to cross-polinate all the way down,
rather than completely depending on higher levels of the hierarchy to maintain a bird’s eye view of the product.</p>
<p>It’s also important to note that it’s a spectrum, not a binary: <em>overfitting</em> and <em>eigensolutions</em> are just its two opposite ends.
Eigensolutions do not come along every day, and do not even exist for all problems.
While it’s important to actively guard against overfitting by making sure solutions are validated by many diverse use cases,
going too far the other side and chasing a general solution for every problem is also a poor use of resources.</p>
<p>Instead, I think a happy medium is to try and be on the right side of the spectrum:</p>
<p><img src="https://lea.verou.me/blog/2023/eigensolutions/images/overfitting.svg" alt="" /></p>
<h2 id="shipping-eigensolutions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#shipping-eigensolutions">Shipping eigensolutions</a></h2>
<p>Good design is only part of the work; but <strong>without shipping, even the most well designed feature is a pointless document</strong>.
Contrary to what you may expect, eigensolutions can actually be quite hard to push to stakeholders:</p>
<ol>
<li>Due to their generality, they often require significantly higher engineering effort to implement.
Quick-wins are easier to sell: they ship faster and add value sooner.
In my 11 years designing web technologies, I have seen many beautiful, elegant eigensolutions be vetoed due to implementation difficulties in favor of far more specific solutions — and often this was the right decision, it’s all about the cost-benefit.</li>
<li>Eigensolutions tend to be lower level primitives, which are more flexible, but can also involve higher friction to use than a solution that is tailored to a specific use case.</li>
</ol>
<p>In many cases, <a href="https://lea.verou.me/blog/2023/eigensolutions/#layering"><em>layering</em></a> can resolve or mitigate both of these issues.</p>
<h2 id="layering" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#layering">Layering with higher level abstractions</a></h2>
<p>My north star product design principle is <em>“Common things should be easy, complex things should be possible”</em> (paraphrasing <a href="https://www.goodreads.com/quotes/8636264-simple-things-should-be-simple-complex-things-should-be-possible">Alan Kay</a> — because common things are not always simple, but it’s common things you want to optimize for),
which in essence is another way of aiming for <a href="https://lea.verou.me/blog/2023/eigensolutions/#floor-ceiling">low floors and high ceilings</a>.</p>
<p>Eigensolutions tend to be lower level primitives.
They enable a broad set of use cases, but may not be the most learnable or efficient way to implement all of them, compared to a tailored solution.
In other words, they make complex things possible, but do not necessarily make common things easy.
<em>Some do both, in which case congratulations, you’ve got an even bigger unicorn! You can skip this section. :)</em></p>
<p>However, this is one of the rare times in life where we can have our cake and eat it too.
Instead of implementing tailored solutions ad-hoc (risking overfitting),
they can be implemented as <strong>shortcuts</strong>: higher level abstractions <em>using</em> the lower level primitive.
Done well, shortcuts provide dual benefit: not only do they reduce friction for common cases,
they also serve as teaching aids for the underlying lower level feature.
This offers a very smooth ease-of-use to power curve:
if users need to go further than what the shortcut provides, they can always fall back on the lower level primitive to do so.
<a href="https://lea.verou.me/blog/2023/eigensolutions/#TBD">We know</a> that tweaking is easier than creating from scratch,
so even when users use that escape hatch, they can tweak what they had created with the higher level UI, rather than starting from scratch.
This combined approach <em>both</em> reduces the floor <em>and</em> increases the ceiling!</p>
<h3 id="example%3A-table-filtering-in-coda" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#example%3A-table-filtering-in-coda">Example: Table filtering in Coda</a></h3>
<p><a href="https://coda.io/">Coda</a> is a product I’ve been using a lot in the last few months.
It has replaced Google Docs, Google Sheets, and a few more niche or custom apps I was using.
Its UI is full of examples of this pattern, but for the sake of brevity, I will focus on one: table filtering.</p>
<p>At first, the filtering UI is pretty high level, designed around common use cases:</p>
<figure>
<p><img src="https://lea.verou.me/blog/2023/eigensolutions/images/coda-filter-1.png" alt="" />
<img src="https://lea.verou.me/blog/2023/eigensolutions/images/coda-filter-2.png" alt="" /></p>
<figcaption>
<p>Also note the nice touch of “And” not just being informative, but also a control that allows the user to edit the logic used to combine multiple filters.</p>
</figcaption>
</figure>
<p>For the vast majority of use cases (I would guess >95%), the UI is perfectly sufficient.
If you don’t need additional flexibility, you may not even notice the little <em><strong>f</strong></em> button on the top right.
But for those that need additional power it can be a lifesaver.
That little <em>f</em> indicates that behind the scenes, the UI is actually generating a <em>formula</em> for filtering.
Clicking it opens a formula editor, where you can edit the formula directly:</p>
<p><img src="https://lea.verou.me/blog/2023/eigensolutions/images/coda-filter-editor.png" alt="" /></p>
<p>I suspect that even for the use cases that require that escape hatch, a small tweak to the generated formula is all that is necessary.
The user may have not been able to write the formula from scratch, but tweaking is easier.
As one data point, the one time I used this, it was just about using parentheses to combine AND and OR differently than the UI allowed.
And as a bonus, the app can collect metrics about what users do with the lower level feature and use that to improve the higher level UI.
It’s a win-win all around.</p>
<h3 id="what-to-ship-first%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#what-to-ship-first%3F">What to ship first?</a></h3>
<p>In an ideal world, lower level primitives and higher level abstractions would be designed and shipped together.
However, engineering resources are typically limited, and it often makes sense to ship one before the other,
so we can provide value sooner.</p>
<p>This can happen in either direction:</p>
<ol>
<li><strong>Lower level primitive first</strong>.
Shortcuts to make common cases easy can ship at a later stage,
and demos and documentation to showcase common “recipes” can be used as a stopgap meanwhile.
This prioritizes use case coverage over optimal UX, but it also allows collecting more data,
which can inform the design of the shortcuts implemented.</li>
<li><strong>Higher level abstraction first</strong>, as an independent, ostensibly ad hoc feature.
Then later, once the lower level primitive ships, it is used to “explain” the shortcut, and make it more powerful.
This prioritizes optimal UX over use case coverage:
we’re not covering all use cases, but for the ones we are covering, we’re offering a frictionless user experience.</li>
</ol>
<p>But which one?
As with most things in life, the answer is “it depends”.</p>
<p>A few considerations are:</p>
<ul>
<li>How many shortcuts do we need? What percentage of use cases do they cover?</li>
<li>How much harder is it to use the lower level primitive directly? Are we certain we will need to provide shortcuts, or is it possible it may be sufficient on its own?</li>
<li>Which one are we more confident about?</li>
<li>How much engineering effort does the lower level primitive require and how does it compare to implementing the shortcuts as ad hoc features?</li>
<li>Do we have extensibility mechanisms in place for users to create and share <em>their own</em> higher level abstractions over the lower level feature?</li>
</ul>
<p>Outside of specific cases,
it’s also good to have a design principle in place about which way is generally favored, which is part of the product philosophy
(the answer to the <a href="https://coda.io/@shishir/eigenquestions-the-art-of-framing-problems/eigenquestions-3">eigenquestion</a>:
<em><strong>“Are we optimizing for flexibility or learnability?”</strong></em>)
and can be used to fall back on if weighing tradeoffs ends up inconclusive.</p>
<p>Note that <strong>even when we don’t think the eigensolution is implementable</strong>,
it can still be useful as a <a href="https://lea.verou.me/blog/2023/eigensolutions/#nsui"><em>north star UI</em></a> and designing the tailored solutions as special cases of it can still be a good idea.</p>
<aside id="nsui">
<h4 id="north-star-ui%3F" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#north-star-ui%3F">North Star UI?</a></h4>
<p>The ideal UI for addressing a set of use cases in a perfect world where we have infinite resources.
This is rarely known to us, but there are cases where we know exactly what the perfect solution would be,
but it’s not feasible due to practical concerns (e.g. implementation challenges), so we need to keep looking.
However, it can still be useful as a guide to steer us in the right direction.
And in some cases, it becomes feasible later on, due to changes in internal or external factors.
In my 11 years of designing web technologies, I have seen many “unimplementable” solutions become implementable later on.</p>
<p>My favorite example, and something I’m proud to have personally helped drive is the current <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting">CSS Nesting syntax</a>.
We had plenty of signal for what the optimal syntax for users would be, but it was vetoed by engineering across all major browsers, so we had to design around certain constraints.
Instead of completely diverging (which <em>could</em> have produced better syntaxes!), we used it as a north star, and designed and shipped a syntax that was a bit more verbose but forwards compatible with it.
Once we got consensus on that, I started trying to get people on board to explore ways (even potential algorithms) to bridge the gap,
until eventually Chrome engineers closed on a way to implement the north star syntax 🎉, and as they say, the rest is history.</p>
</aside>
<p>In the web platform we’ve gone back and forth on this a lot.
In the beginning, the Web skewed towards shipping higher level abstractions.
It had a low floor, but also a relatively low ceiling: many capabilities required browser plugins, or desktop applications.
The <a href="https://extensiblewebmanifesto.org/">Extensible Web Manifesto</a> was created as a reaction,
urging standards groups to design low level primitives first.
For a while, this became the gold standard and many new features were very low level.
This filled some necessary gaps in the platform, but since resources are limited, the layering was often missed, resulting in <em>only</em> low level primitives which were a pain to use.
More recently, we’ve been <a href="https://www.w3.org/TR/design-principles/#high-level-low-level">recommending</a> a more balanced approach, where tradeoffs are evaluated on a case by case basis.</p>
<h2 id="example" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#example">A fictional example: TableSoda</a></h2>
<p>Suppose we were working on a fictional product that is an improvement over spreadsheets, let’s call it <em>TableSoda</em>.
It has several features that make it more powerful and user-friendly than spreadsheets:</p>
<ul>
<li>It allows users to have multiple tables and define formulas or datatypes for a whole column</li>
<li>It also supports references from a cell of one table to a row of another table.</li>
<li>Its formula language supports operations on entire columns, and can return entire rows from other tables.</li>
<li>Each table can be shared with different people, but a given user can either see/edit all the rows and columns of a table, or none.</li>
</ul>
<p>Some of the use cases in TableSoda’s <a href="https://lea.verou.me/blog/2023/eigensolutions/#use-case-backlog">use case backlog</a> are:</p>
<ul>
<li>Pivot tables: tables that display stats about the usage of a value in another table (usually counts but also sum, min, max, average, etc.)<sup class="footnote-ref"><a href="https://lea.verou.me/blog/2023/eigensolutions/#fn2" id="fnref2">[2]</a></sup></li>
<li>Unions of multiple tables. For example, combining a table of debits and a table of credits into a table of transactions.</li>
<li>Vertical splitting: Multiple tables augmenting the same data with different metadata. For example, a table of product features, another table that scores these features on various factors, and lastly, a table of 👍🏼 reactions by different team members about each feature.</li>
<li>Granular access control, by row(s) or column(s). For example, a table of tasks where each row is assigned to a different team member, and each team member can only see their own tasks and only edit the status column.</li>
</ul>
<p>With the traditional PM mindset, we would prioritize which one(s) of these is most important to solve, design a few possible solutions, evaluate tradeoffs between them.
Over time, we may end up with a pivot table feature, a table union feature, a table vertical split feature, a row-level access control feature, and a column-level access control feature.
These features would not necessarily be overfitting, they may solve their respective use cases quite well.
But they also add a lot of complexity to the product.</p>
<p>Instead, we would still prioritize which one to address first, but with the mindset of decomposing it to its essential components
and addressing those (note that there may be many different possible decompositions).
Suppose we decide that we want to prioritize pivot tables.
A pivot table is essentially<sup class="footnote-ref"><a href="https://lea.verou.me/blog/2023/eigensolutions/#fn2" id="fnref2:1">[2:1]</a></sup>:</p>
<ul>
<li>A table of all unique values in the source column</li>
<li>For each unique value, columns with its count, sum, etc. in the source column</li>
</ul>
<p>Users can already count the number of values in a column using formulas, and they can also use a <code>unique()</code> formula to get a list of unique values in a column.
So what prevents them from creating their own pivot tables?
There is no way to create dynamic tables in TableSoda, rows can only be added by users.
What if we could populate a table’s rows via a formula? The formula values could be used either for one column or multiple (if it returns a list of objects).</p>
<p>Formula-populated tables not only solve our driving use case, but all of the above:</p>
<ul>
<li>Unions can be implemented by using a formula to concatenate the rows of multiple tables into a single list.</li>
<li>Vertical splitting can be implemented by using a formula to keep the rows of multiple tables in sync with a master table</li>
<li>Granular access control can be implemented by having a table with different permissions that is populated using a formula that filters the rows and/or columns of the source table.</li>
</ul>
<p>It’s an eigensolution!</p>
<p>Note that our eigensolution is not the end for any of our use cases.
It makes many things <em>possible</em>, but none of them are <em>easy</em>.
Some of them are common enough to warrant a <a href="https://lea.verou.me/blog/2023/eigensolutions/#layering">shortcut</a>: UI that generates the formula needed.
For others, our solution is more of a <em>workaround</em> than a primary solution, and the search for a primary solution continues, potentially with reduced prioritization.
And others don’t come up often enough to warrant anything further.
But even if we still need to smoothen the ease-of-use to power curve, making things <em>possible</em> bought us a lot more time to make them <em>easy</em>.</p>
<h2 id="use-cases-as-the-testsuite-of-product-design" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#use-cases-as-the-testsuite-of-product-design">Use cases as the testsuite of product design</a></h2>
<p>The most discerning of readers may have noticed that despite the name <em>eigensolution</em>, it’s still all about the use cases:
<strong>eigensolutions just expose links between use cases that may have been hard to detect, but seem obvious in retrospect</strong>.
In the example above, one <em>could</em> have seen in advance that all of these use cases were fundamentally about dynamically populating tables.
But wasn’t it <em>so</em> much easier to see in retrospect?</p>
<p>Requiring all use cases to precede any design work can be unnecessarily restrictive,
as frequently solving a problem improves our understanding of the problem.</p>
<p>Joe McLean (of <a href="https://miro.com/">Miro</a>) <a href="https://bootcamp.uxdesign.cc/overfitting-and-the-problem-with-use-cases-337d9f4bf4d7">takes a more extreme position</a>:</p>
<blockquote>
<p>I believe it’s best to think of a use case as a test case to see if your basic tools are working. What’s missing from the toolbox? What are the limits of what’s available? What 4 use cases would open up with the addition of one more tool?</p>
<p>Use cases should be applied after design is done — to check if the tools available can accomplish the job. As a starting point, they put you in a mindset to overfit. This is especially dangerous because users will often tell you they love it in concept testing. “Ah yes, here is my process, represented in pictures!” But it’s only when you actually try to use the tool — hold the thing in your hands — that there’s a hundred things you need it to do that it doesn’t. It’s not flexible — it’s a series of menus and disappointed feature requirements.</p>
</blockquote>
<p>Joe argues for using use cases <em>only</em> at the end, to validate a design, as he believes that starting from use cases leads puts you in a mindset to overfit.
This is so much the polar opposite of current conventional wisdom, that many would consider it heresy.</p>
<p>I think that <em>also</em> imposes unnecessary constraints on the design process.
I personally favor a more iterative process:</p>
<ol>
<li>Collect as many diverse use cases as possible upfront to drive the design</li>
<li>Additional use cases are used to refine the design until it stabilizes</li>
<li>Even more at the end to validate it further.</li>
</ol>
<p>If you’re on the right path, additional use cases will smoothly take you from refinement to validation as the design stabilizes.
If you’re not on the right path, they will expose fundamental flaws in your design and show you that you need to start over.</p>
<p>This has some similarities to test-driven development in engineering:
engineers start with a few test cases before writing any code,
then add more as they go to make sure everything works as expected.</p>
<p>But if someone else’s design thinking works best with using use cases only for validation, more power to them!</p>
<p><strong>What matters is that the outcome is a solution that addresses a broad set of use cases in a way users can understand and use.</strong>
We can probably all agree that no proposal should be considered without being <strong>rigorously</strong> supported by use cases.
It is not enough for use cases to exist;
they need to be sufficiently diverse and correspond to <em>real</em> user pain points that are <em>common</em> enough to justify the cost of adding a new feature.
But whether use cases drove the design, were used to validate it, or a mix of both is irrelevant,
and requiring one or the other imposes unnecessary constraints on the design process.</p>
<p><em>Thanks to Marily Nika and Elika Etemad for providing feedback on an earlier draft of this post.</em></p>
<h2 id="notable-reactions" tabindex="-1"><a class="header-anchor" href="https://lea.verou.me/blog/2023/eigensolutions/#notable-reactions">Notable reactions</a></h2>
<p>I hesitantly published this article right before the 2023 winter break.
I say hesitantly, because it was a departure from my usual content, and I wasn’t sure how it would be received.
I was elated to see that despite its length, somewhat intimidating title, and publication date, it did get some very validating reactions.</p>
<p>My favorite was <a href="http://people.csail.mit.edu/dnj/">Daniel Jackson</a>’s <a href="https://forum.essenceofsoftware.com/t/lea-verou-post-eigensolutions-composability-as-the-antidote-to-overfit/184">insightful summary of the ideas presented</a>:</p>
<blockquote>
<p>I just came across an <a href="https://lea.verou.me/blog/2023/eigensolutions/">excellent post </a> by Lea Verou which argues for building software on more general and composable abstractions.</p>
<p>In short, I see several different ideas at play in her piece:</p>
<ul>
<li>Use cases lead to overfitting and it’s better to design more coherent and general increments of function;</li>
<li>More complex and domain-specific functionality can often be obtained as an instantiation or composition of more abstract and general functionality;</li>
<li>Even if you don’t implement the more general and abstract functionality, it might be better to design it and think of your implementation as partial;</li>
<li>You can use progressive disclosure in the UI as a bridge between more common domain-specific functionality and more general functionality.</li>
</ul>
<p>These ideas seem to have a lot in common with concept design. Maybe her eigensolutions are concepts? What do y’all think? Also, I really liked the critique of use cases, which connects to our <a href="https://forum.essenceofsoftware.com/t/how-related-are-nakedobjects-by-pawlson-and-concept-oriented-design/177/9">discussion</a> last year of Bertrand Meyer’s piece.</p>
</blockquote>
<p>It was very validating to see that the ideas resonated with someone who has been thinking about good conceptual design so deeply that it’s his primary area of research at MIT for years, and has published an <a href="https://essenceofsoftware.com/">excellent book</a> on the matter (I only started reading it recently, but I’m loving it so far).</p>
<p>It was also validating to see that the ideas resonated with <a href="https://shishir.io/">Shishir Mehrotra</a> (CEO of <a href="https://coda.io/">Coda</a>), who <a href="https://www.linkedin.com/feed/update/urn:li:activity:7142948935676882945?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7142948935676882945%2C7145299080624103424%29&dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287145299080624103424%2Curn%3Ali%3Aactivity%3A7142948935676882945%29">commented</a>:</p>
<blockquote>
<p>Very insightful article, loved it!</p>
</blockquote>
<p>If you recall, it was him who coined the term <em>eigenquestion</em> that inspired the term <em>eigensolution</em>.</p>
<p><a href="https://www.threads.net/@dfosco%5BTitle%5D(https://www.threads.net/@dfosco)">Daniel Fosco</a> (Software designer at <a href="https://miro.com/">Miro</a>) <a href="https://www.threads.net/@dfosco/post/C1FGfODoxSp">reposted and wrote</a>:</p>
<blockquote>
<p>This is by far the best design article I’ve read in a very long time. Lea dives right into what it takes to build complex tools that have to meet wide, unmapped user needs.
I also love how it does not shy away from the complexity of the topic even for a moment: on the contrary, the title is already telling you what you’re signing up for.
@leaverou is no stranger to great writing, but this one is truly a gem.</p>
</blockquote>
<p>I recently started using <a href="https://miro.com/">Miro</a> myself, for diagrams and wireframes (most illustrations in this article have been made with Miro), and there are some real gems in its design, so it was very validating to see that the ideas resonated with someone who works on designing it.</p>
<p>Fredrik Matheson (Creative Director at Bekk) <a href="https://www.linkedin.com/posts/movito_whats-the-opposite-of-overfitting-eigensolutions-activity-7143012800225861632-i_Lw?utm_source=share&utm_medium=member_desktop">reposted and wrote</a>:</p>
<blockquote>
<p>Are you new to UX? This post will be a bit like taking an elevator up above the clouds, where you can see further, beyond the constraints of the transactional systems you might be working on already. Recommended.</p>
</blockquote>
<p>He even subsequently proceeded to quote concepts from it in a <a href="https://www.linkedin.com/feed/update/urn:li:activity:7143508856587911169?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7143508856587911169%2C7144244558615269376%29&dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287144244558615269376%2Curn%3Ali%3Aactivity%3A7143508856587911169%29">number of</a> <a href="https://www.linkedin.com/feed/update/urn:li:ugcPost:7148246579894800384?commentUrn=urn%3Ali%3Acomment%3A%28ugcPost%3A7148246579894800384%2C7148248066465193984%29&dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287148248066465193984%2Curn%3Ali%3AugcPost%3A7148246579894800384%29">comments</a> on other posts! 🤩</p>
<p><a href="https://www.linkedin.com/in/natebaldwindesign/">Nate Baldwin</a> (Principal Product Designer at Intuit) <a href="https://twitter.com/NateBaldwinArt/status/1738748897928077400">reposted and wrote</a>:</p>
<blockquote>
<p>This is a wonderful article! What @LeaVerou defines is what I consider platform design, which I think sits one level below UI systems design. Ie:</p>
<p>Product design
⬇️
Systems design (UI)
⬇️
Platform design</p>
<p>Although her approach to design is relevant to each.</p>
</blockquote>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>I’ve spent so long designing creator tools that I tended to assume my observations and learnings from my experience are universal.
I first read about this distinction in Joe Mc Lean’s
excellent post on <a href="https://bootcamp.uxdesign.cc/overfitting-and-the-problem-with-use-cases-337d9f4bf4d7">overfitting</a>, and it was a bit of an a-ha moment. <a href="https://lea.verou.me/blog/2023/eigensolutions/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Yes, pivot tables are more complex than that, but let’s keep it simple for the sake of the example. <a href="https://lea.verou.me/blog/2023/eigensolutions/#fnref2" class="footnote-backref">↩︎</a> <a href="https://lea.verou.me/blog/2023/eigensolutions/#fnref2:1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>