Since javascript is everywhere nowadays, it is really easy to learn new stuff everyday. Once you know the basics of the language, you can take bits of code from here and there that have a lot of knowledge in them. Bookmarklets are perfect examples of a bunch of packed functionality, whenever I discover a useful one, I enjoy studying their source, discovering how it can do so much. Also snippets to use external services, like the google analytics code, or facebook likebox, can teach us more than some books.
Today I want to break in pieces a one-liner code that Addy Osmani shared a few days ago that can be used to debug your CSS layers. Here it is, in 3 lines to fit in the post:
1 2 3 |
[].forEach.call($$("*"),function(a){ a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16) }) |
Try to type it in your browser console, and the different layers of HTML that there are in the web page will be highlighted in different colors. Isn’t it awesome? Basically, it gets all the elements of the page, and applies a 1 px outline to them with a random color. The idea is simple, but to create a code line like this you must master a lot of aspects of web development. Let’s study them.
TL;DR
People from Webucator made a video about this article, so you can watch instead of read. Isn’t it great? If you are learning Javascript, checking Webucator javascript training is a must.
Selecting all the elements of a page
What it is needed the first is to get all the elements, and Addy uses the function $$
that is only available in the console of browsers. Try it by yourself, open your browser’s javascript console and type $$('a')
and you will get a list with all the anchor elements of the current page.
$$
function is part of the command line API of modern browsers, and it is equivalent to use the method document.querySelectorAll
, you can pass a CSS selector as argument to get the matched elements in the current page. So if you would like to use the one-liner out the browser’s console you could replace $$('*')
by document.querySelectorAll('*')
. More about the function $$
can be found in this stackoverflow answer.
It is great! For me, it was worthy to study the code just by meeting the function $$
. But there are more about selecting everything in a page, if you have a look at the comments of the gist, there are people discussing this part of the code. One of them is Mathias Bynens, ( a lot of clever people there! ) who suggests that we can also use document.all
to select all the elements of a page, it is not standard, but it works ok in every browser but firefox (in FF too).
Iterating over the elements
So we have all the elements now as a NodeList
and we want to go through all of them applying the colorful outline. But wait, what the heck is used in our code?
1 |
[].forEach.call( $$('*'), function( element ) { /* And the modification code here */ }); |
NodeLists
seems like Arrays
, you can access to their nodes using brackets, and you can check how many elements it contains using the property length, but they doesn’t implement all the methods of the Array
interface, so using $$('*').forEach
will fail. In Javascript there are several objects that look like arrays but they are not, like the arguments variable inside functions, and we have here a very useful pattern to handle with them: Calling array methods on no-array objects as NodeLists
is possible using the Function’s methods call and apply. I wrote about those functions some months ago, they execute a function using the first parameter as the object this inside of the function
1 2 3 4 5 6 7 8 9 10 |
function say(name) { console.log( this + ' ' + name ); } say.call( 'hola', 'Mike' ); // Prints out 'hola Mike' in the console // Also you can use it on the arguments object function example( arg1, arg2, arg3 ) { return Array.prototype.slice.call(arguments, 1); // Returns [arg2, arg3] } |
The one-liner is using [].forEach.call
instead of Array.prototype.forEach.call
to save some bytes ( another nice trick yeah? ) calling the method in the Array
object []
. This would be equivalent to $$('*').forEach
if $$('*')
value was an Array
.
If you have a look at the comments again, there are some people who use for(i=0;A=$$('*');)
instead to make the code shorter. It works, but it is leaking global variables, so if you want to use the code out of the console, you better get a clean enviroment using
1 |
for(var i=0,B=document.querySelectorAll('*');A=B[i++];){ /* your code here */ } |
If you use it in the browser’s console it doesn’t really matter, the variables i
and A
will be available there since you are declaring them there.
Assigning colors to the elements
To make the elements have that nice border, the code is using the CSS property outline
. In case you don’t know, the rendered outline is out of the CSS box model so it doesn’t affect the size of the element or its position in the layout, so it is perfect for this purpose. It’s syntax is like the one used for the border
property, so it shouldn’t be difficult to understand this part:
1 |
a.style.outline="1px solid #" + color |
What is interesting is how the color is defined:
1 |
~~(Math.random()*(1<<24))).toString(16) |
Scary uh? Sure. I am not a bit-wise operations expert, so this is the part I liked the most, because it let me learn a lot of new stuff.
What we want to achieve is a hexadecimal color like white FFFFFF
or blue 0000FF
or… who knows… 37f9ac
. Non super-human people, like me, is used to work with decimal numbers, but our beloved code knows a lot about hexadecimal ones.
First thing it can teach us is how to convert a decimal number to hex using the method toString
for integers. The method accepts a parameter to convert a number to a string using a base number of characters. If the parameter is not used, 10 characters are used (0…9, hence, decimal numbers), but you can use whatever other base:
1 2 3 4 5 |
(30).toString(); // "30" (30).toString(10); // "30" (30).toString(16); // "1e" Hexadecimal (30).toString(2); // "11110" Binary (30).toString(36); // "u" 36 is the maximum base allowed |
The other way around, you can convert hexadecimal string to decimal numbers using the second parameter of the parseInt
method:
1 2 3 4 5 |
parseInt("30"); // "30" parseInt("30", 10); // "30" parseInt("1e", 16); // "30" parseInt("11110", 2); // "30" parseInt("u", 36); // "30" |
So we need a random number between 0
and ffffff
in hexadecimal, that is parseInt("ffffff", 16) == 16777215
and 16777215 is exactly 2^24 - 1
.
Do you like binary maths? If not, you will be ok knowing that 1<<24 == 16777216
(try it in the console).
If you like them, you need to know that every time that you add a 0 to the right of a 1 you are doing performing the 2^n
operation, being n
the number of 0s you add.
1 2 3 4 |
1 // 1 == 2^0 100 // 4 == 2^2 10000 // 16 == 2^4 1000000000000000000000000 // 16777216 == 2^24 |
The left shift operation x << n
adds n
0
s to the binary representation of the number x
, so 1<<24
is a short way of saying 16777216
, and doing Math.random()*(1<<24)
we get a number between 0
and 16777216
.
We are not ready yet, because Math.random
return a float number, and we need only the whole part. Our code use the tilde operator to get so. Tilde operator is used to negate a variable bit by bit. If you don’t know about what I am talking about, here it is a good explanation: Javascript’s tilde operator.
But the code doesn’t care about bitwise negation, it uses the tilde because the bitwise operations discard the decimal part of a float number, so bitwise-negation applied twice is a short way of writing parseInt
:
1 2 3 4 5 6 7 8 |
var a = 12.34, // ~~a = 12 b = -1231.8754, // ~~b = -1231 c = 3213.000001 // ~~c = 3213 ; ~~a == parseInt(a, 10); // true ~~b == parseInt(b, 10); // true ~~c == parseInt(c, 10); // true |
Again, if you go to the gist and have a look at the comments you will realize that people there is using a shorter version to get the parseInt result. Using the bitwise OR operator you can get rid of the decimal part of our random number
1 2 3 |
~~a == 0|a == parseInt(a, 10) ~~b == 0|b == parseInt(b, 10) ~~c == 0|c == parseInt(c, 10) |
Or operator is the last to be used in a operation so the parenthesis are not needed anymore. Here it is the precedence of javascript operators in case you are interested in having a look.
Finally we have our random number 0
and 16777216
, our random color. We just need to turn it to a hexadecimal string using toString(16)
to make it work.
Last thoughts
Being a programmer is not easy. We are used to code like crazy and sometimes we don’t realize about how much knowledge is needed to do what we do. It takes a long long time to learn and internalize all the concepts that we use in our job.
I wanted to highlight the complexity of our job because I know that programmers are usually underestimated, ( especially in my country, Spain ) and it is nice to say ocassionally that we are really worthy and a key part of most of companies nowadays.
If you understood the one-liner code at first sight you can feel proud of yourself.
If not, but you have reach this point of the article, don’t worry, you will be able to write lines like that soon, you are a learner!
If you thought tl;dr at the second line of the article but you are reading this, you are really weird, but your thoughts are also welcome in the comments section below : )
at 2:53 pm
This color randomizer
(~~(Math.random()*(1<<24))).toString(16)
is awesome !at 3:50 pm
Indeed! In the original gist there is an even nicer way of getting the color:
Math.random().toString(16).slice(-6)
at 8:57 am
That looks so much easier. Any difference with the one from Addy Osmani? Seems like it will just generate random HEX on 6 chars too, after all.
at 7:26 am
Technically the slice option is better, because
0|Math.random()*(1<<24).toString(16)
can result in a hexadecimal string of length other than 6 (or 3). Those strings are invalid (at least in my Firefox) color values, so the outline would not have a color set. Padding with zeroes would work of course. But I don’t see a short way of doing that.Though there is a miniscule chance that
Math.random()
returns a short float, which would result in theslice(-6)
containing the decimal point, also making that an invalid color string.at 8:57 am
Unfortunatelly for this task,
Math.random
also can return numbers like 0.4, so slice option also can generate invalid colors. You will need to pad it or use the hsl colors version that is in the original gist.at 7:26 am
Actually there is a very simple and short way of padding the result with zeroes — just append them as a string before using the
slice
operation:(Math.random().toString(16)+'0000000').slice(2, 8)
The intermediate result must be padded with at least seven characters since
Math.random()
may return zero.at 10:22 pm
Make your life awesome:
setInterval(function () { [].forEach.call($$("*"),function(a){
a.style.outline="2px solid #"+(~~(Math.random()*(1<<12))).toString(16) }, 5);
}, 100)
at 7:47 am
Geocities is back!!! Yeaah! I will create a chrome extension and apply it automatically to every website :D
at 6:01 am
Yeah, that’s great! Takes me back to the 90s!
;-)
at 2:06 pm
Epilepsy ?
setInterval(function () { [].forEach.call($$(“*”),function(a){
a.style.backgroundColor=”#”+(~~(Math.random()*(1<<12))).toString(16) }, 5);
}, 100)
at 11:15 am
I bet you will enhjoy this oneliner http://stackoverflow.com/questions/3746725/create-a-javascript-array-containing-1-n
at 7:57 pm
Great article! I like it very much, I have learnt a couple of things interesting. Every day there is something new to learn from Javascript, and I have been working with JS since 8 years ago.
About Spanish developers, I agree with your opinion. Best regards from Madrid.
at 10:51 am
The algorithm is clever, indeed I learned a lot with just one line of code. Thanks man!
at 11:47 am
Great Explanation ! Great Article !
at 1:13 pm
Took the oneliner, swapped
$$
fordocument.querySelectorAll
, and arrived at this bookmarklet:javascript:[].forEach.call(document.querySelectorAll(“*”),function(a){a.style.outline=”1px solid #”+(~~(Math.random()*(1<<24))).toString(16)})
at 2:48 pm
this is cool, but writing one-liner as such greatly reduces its readability, dont you think
at 2:55 pm
Nice one! Thanks for sharing.
at 5:40 pm
Really nice. Thanks for the great explanation.
at 8:07 pm
Here’s a nice bookmarklet that achieves a similar effect. :) thanks for breaking down this function to pieces.
http://pesticide.io
at 1:35 am
这一小段知识量好大~~~看完我感觉整个人都萌萌哒
at 8:51 am
Tiny mistake:
> What is interesting is how the color is defined:
> ~~(Math.random()*(1<<24))).toString(16)
The open parenthesis on the left is missed:
(~~(Math.random()*(1<<24))).toString(16)
at 3:04 pm
I really enjoyed the article and explanation, but this is nice as general knowledge. I strongly recommend against such code when you are working on a project with other people, or when you know that in the future someone else will need to maintain your code. This kind of code and similar, usually costs a lot of time, because it’s hard to understand, and the cost is higher than the benefit, in the long run.
at 7:37 pm
Just because you can use
~~
forparseInt
doesn’t mean you should. Coding is a social activity, and abusing operators to do unrelated tasks makes code reading harder for everybody.at 2:27 pm
Of course you shouldn’t. But on this self-contained, meant to be smaller as possible code, that is okay.
On production code is not cool to change from
parseInt
to~~
, specially with data you receive from somewhere else.If you receive a float with leading zero it will throw a SyntaxError, same for
0|
.And if you for some reason you expect a float but receive an int with leading zero, it will work as
parseInt
without radix, that is,~~
and0|
will return octal.Therefore it’s use is very situational.
at 2:30 pm
Good notes Raphael, thanks for sharing.
at 5:46 am
You made my day. Thanks a lot!
I always want all the articles or tutorial on the internet to explain the way you did. Thanks again :)
at 7:12 pm
I think these one could a start of series of articles about one line of code. Would you mind to write such series?
at 8:34 am
If I find oneliners as interesting as this one, no doubt, I will.
at 5:10 am
Everything is fine but i am unable to get why function is using $$ instead of $.
Because i hav’nt used $$ till now to select DOM elements.Can u please provide clarification on it?
at 8:33 am
This code is meant to be used in the browser’s console and without jQuery. In the console, the
$
is also available to select nodes, but there are a lot of chances that the current page has overriden the variable$
using some library, like jQuery or MooTools. Using$$
we have more chances of using browser node selection.Never use
$$
outside the console, it won’t work unless you are using MooTools.at 12:34 pm
Thanks :)
at 6:50 am
parseInt is not doing the same thing the other methods are doing. For exlampe, Number( 2px ) is NaN, but parseInt( 2px , 10) is 2.The key difference is that Number (and the implicit conversions) will try to convert the whole thing, and if they fail will return NaN, while parseInt will parse as much as it can and return that.
at 4:50 pm
Awesome article, though I could understand what the code could do, your article explained things a lot more in depth, hope I can reach such level soon
at 7:32 am
Awesome article. Learnt the usage of several components of JS and CSS through this one line of code snippet.
at 11:06 am
interesting article.
small typo: “…and doing Math.random()*/(1<<24) we get…" needs to be "…and doing Math.random()*(1<<24) we get…" (remove the slash)
at 9:32 am
Fixed, thanks for pointing it out.
at 6:36 am
Please note that all logical ooeratprs like ~, , >>>, & and | internally operates on a 32 bit integer, while Numbers in javascript is 64 bit floats. Try to convert 2147483648 and notice the sign.Check out the jsperfs page for other variants.However, I’m not sure if the numbers given by jsperf is accurate in this case. When using randomized data I don’t get that much of a difference between the different methods. I would like to see the code that jsperf is running. I think there might be a hidden compilation that we don’t see:Try this:Function( return ~~(1*’12 ) ).toString();In firefox I get:function anonymous() { return 12;}Notice that the expression has been compiled to 12. No wonder that it is faster!
at 7:24 pm
Using <> to multiply/divide by powers of 2 was a very well known trick in C or assembly back to the times when you couldnt waste one cpu cycle. You also used to write xor ax, ax to optimize registers cleaning… Memories, memories… js is the web’s assembly.
at 11:18 am
This also works in some browsers:
for(var a of $$(“*”)){
//…
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for…of
at 10:35 pm
Great breakdown. I had fun breaking your page with [].forEach.call – I’ve been looking for a way to call a forEach loop on nodes.
at 6:21 am
Good share!
at 6:49 am
> In one of the tests that where added to your jsperf there was one that did “0+’12′”. That aclaltuy returns a string.That was me, even if I should have known better: I wanted to test if binary plus suffers from the same poor performance in Firefox as unary plus, but forgot about the fact that Javascript’s plus genrally doesn’t force numeric conversions. I blame it on too much Perl coding .I couldn’t see any pattern in the set of operators which are fast (eg ~~’12 , ’12 -0, ’12 <<0) and those which aren't (eg +'12', '12'|0).
at 2:33 pm
nice one,thank you for sharing
at 9:39 pm
Thanks for the article, it was a fun and insightful read. Please consider that some of your audience aren’t always male – so the conclusion where said ‘man’ was off-putting to me: “If not, but you have reach this point of the article, don’t worry man, you will be able to write lines like that soon, you are a learner!”
at 2:30 pm
Sorry Kelly, I didn’t mean to. Fixed.
at 9:38 am
very useful ,as a programmer where in China,i want say 谢谢
at 11:47 pm
This is a very nice post because it demonstrates an original and inspired method of learning!
at 6:57 am
Great written and designed article.
at 8:17 am
Nice one,although some parts make me puzzled
at 1:09 pm
This is great stuff – more! More!
:)
at 12:26 am
We made the first one liner code of Addy Osmani a Bookmarklet: Easier to use ;)
https://twitter.com/mohammedalaa/status/528703992562319360
at 2:37 am
You didn’t really explain how you got 24 as the base log. But for those wondering, here is a way:
function getBaseLog(x, y) {
return Math.log(y) / Math.log(x);
}
getBaseLog(2, parseInt(‘ffffff’, 16) + 1) // 24
at 5:34 pm
One problem is that
(~~(Math.random()*(1<<24))).toString(16)
can give something like"6356f"
or even"1"
(note: length is less than 6). This causes the style to be considered invalid, and is ignored by the browser. This causes about 6% of elements to be unstyled. You need to left-pad with 0s.("000000"+(~~(Math.random()*(1<<24))).toString(16)).slice(-6);
at 7:32 am
I have learned much.