{ "version": "https://jsonfeed.org/version/1", "title": "Tyler Cipriani: pages tagged javascript", "home_page_url": "https://tylercipriani.com/tags/javascript/", "feed_url": "https://tylercipriani.com/tags/javascript/index.json", "items": [ { "id": "https://tylercipriani.com/blog/2016/03/21/Visualizing-Git-Merkle-DAG-with-D3.js/", "title": "Visualizing Git’s Merkle DAG with D3.js", "url": "https://tylercipriani.com/blog/2016/03/21/Visualizing-Git-Merkle-DAG-with-D3.js/", "author": { "name": "Tyler Cipriani" }, "tags": [ "computing", "javascript", "vcs" ], "date_published": "2016-03-21T00:00:00Z", "date_modified": "2017-07-01T00:49:09Z", "content_html": "
The Directed Acyclic Graph (DAG) is a concept I run into over-and-over again; which is, perhaps, somewhat ironic.
\nA DAG is a representation of vertices (nodes) that are connected by directional edges (arcs—i.e., lines) in such a way that there are no cycles (e.g., starting at Node A
, you shouldn’t be able to return to Node A
).
DAGs have lots of uses in computer science problems and in discrete mathematics. You’ll find DAGs in build-systems, network problems, and, importantly (for this blog post, if not generally) in Git.
\nOne way to think of a DAG is as a set of dependencies—each node may have a dependency on one or more other nodes. That is, in order to get to Node B
you must route through Node A
, so Node B
depends on Node A
:
The visualization of dependencies in a JSON object is (SURPRISE!) different from the input format needed to visualize a DAG using the D3.js Force layout. To change the above object into Force’s expected input, I created a little helper function:
\nvar forceFormat = function(dag) {\n var orderedNodes = [],\n nodes = [],\n links = [],\n usesPack = false;\n\n // Basically a dumb Object.keys\n for (node in dag) {\n if ( !dag.hasOwnProperty( node ) ) continue;\n orderedNodes.push(node);\n }\n\n orderedNodes.forEach(function(node) {\n var sources = dag[node];\n\n if (!sources) return;\n\n sources.forEach(function(source) {\n var source = orderedNodes.indexOf(source);\n\n // If the source isn't in the Git DAG, it's in a packfile\n if (source < 0) {\n if (usesPack) return;\n source = orderedNodes.length;\n usesPack = true;\n }\n\n links.push({\n 'source': source,\n 'target': orderedNodes.indexOf(node)\n });\n });\n nodes.push({'name': node});\n });\n\n // Add pack file to end of list\n if (usesPack) nodes.push({'name': 'PACK'});\n\n return { 'nodes': nodes, 'links': links };\n};\n\nvar forceInput = forceFormat(dag);
forceFormat
outputs a JSON object that can be used as input for the Force layout.
{\n "links": [\n {\n "source": "Node A",\n "target": "Node B"\n }\n ],\n "nodes": [\n { "name": "Node A" },\n { "name": "Node B" }\n ]\n}
I can pass this resulting JSON object off to a function that I created after a long time staring at one of mbostock’s many amazing examples to create a D3 Force graph of verticies and edges:
\n// http://bl.ocks.org/mbostock/1138500\nvar makeGraph = function(target, graphData) {\n var target = d3.select(target),\n bounds = target.node().getBoundingClientRect(),\n fill = d3.scale.category20(),\n radius = 25;\n\n var svg = target.append('svg')\n .attr('width', bounds.width)\n .attr('height', bounds.height);\n\n // Arrow marker for end-of-line arrow\n svg.append('defs').append('marker')\n .attr('id', 'arrowhead')\n .attr('refX', 17.5)\n .attr('refY', 2)\n .attr('markerWidth', 8)\n .attr('markerHeight', 4)\n .attr('orient', 'auto')\n .attr('fill', '#ccc')\n .append('path')\n .attr('d', 'M 0,0 V 4 L6,2 Z');\n\n var link = svg.selectAll('line')\n .data(graphData.links)\n .enter()\n .append('line')\n .attr('class', 'link')\n .attr('marker-end', 'url(#arrowhead)');\n\n // Create a group for each node\n var node = svg.selectAll('g')\n .data(graphData.nodes)\n .enter()\n .append('g');\n\n // Color the node based on node's git-type (otherwise, hot pink!)\n node.append('circle')\n .attr('r', radius)\n .attr('class', 'node')\n .attr('fill', function(d) {\n var blue = '#1BA1E2',\n red = 'tomato',\n green = '#5BB75B',\n pink = '#FE57A1';\n\n if (d.name.endsWith('.b')) { return red; }\n if (d.name.endsWith('.t')) { return blue; }\n if (d.name.endsWith('.c')) { return green; }\n return pink;\n });\n\n node.append('text')\n .attr('y', radius * 1.5)\n .attr('text-anchor', 'middle')\n .attr('fill', '#555')\n .text(function(d) {\n if (d.name.length > 10) {\n return d.name.substring(0, 8) + '...';\n }\n\n return d.name;\n });\n\n // If the node has a type: tag it\n node.append('text')\n .attr('text-anchor', 'middle')\n .attr('y', 4)\n .attr('fill', 'white')\n .attr('class', 'bold-text')\n .text(function(d) {\n if (d.name.endsWith('.b')) { return 'BLOB'; }\n if (d.name.endsWith('.t')) { return 'TREE'; }\n if (d.name.endsWith('.c')) { return 'COMMIT'; }\n return '';\n });\n\n var charge = 700 * graphData.nodes.length;\n\n var force = d3.layout.force()\n .size([bounds.width, bounds.height])\n .nodes(graphData.nodes)\n .links(graphData.links)\n .linkDistance(150)\n .charge(-(charge))\n .gravity(1)\n .on('tick', tick);\n\n // No fancy animation, tick amount varies based on number of nodes\n force.start();\n for (var i = 0; i < graphData.nodes.length * 100; ++i) force.tick();\n force.stop();\n\n function tick(e) {\n // Push sources up and targets down to form a weak tree.\n var k = -12 * e.alpha;\n\n link\n .each(function(d) { d.source.y -= k, d.target.y += k; })\n .attr('x2', function(d) { return d.source.x; })\n .attr('y2', function(d) { return d.source.y; })\n .attr('x1', function(d) { return d.target.x; })\n .attr('y1', function(d) { return d.target.y; });\n\n node\n .attr('transform', function(d) {\n return 'translate(' + d.x + ',' + d.y + ')';\n });\n }\n};\nmakeGraph('.merkle-1', forceInput);
You’d be forgiven for thinking that is a line.
\nThis directional line is a DAG—albeit a simple one. Node B
depends on Node A
and that is the whole graph. If you want to get to Node B
then you have to start at Node A
. Depending on your problem-space, Node B
could be many things: A place in Königsberg, a target in a Makefile (or a Rakefile), or (brace yourself) a Git object.
In order to understand how Git is a DAG, you need to understand Git “objects”:
\n$ mkdir merkle\n$ cd merkle\n$ echo 'This is the beginning' > README\n$ git init\n$ git add .\n$ git -m 'Initial Commit'\n$ find .git/objects/ -type f\n.git/objects/1b/9f426a8407ffee551ad2993c5d7d3780296353\n.git/objects/09/8e6de29daf4e55f83406b49f5768df9bc7d624\n.git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da
What are Git objects? Because they look like nonsense:
\n\nAfter a little digging through the Pro Git book, Git objects are a little less non-sensicle. Git objects are simply zlib
compressed, formatted messages:
$ python2 -c 'import sys,zlib; \\\n print zlib.decompress(sys.stdin.read());' \\\n < .git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da\ncommit 195tree 098e6de29daf4e55f83406b49f5768df9bc7d624\nauthor Tyler Cipriani <tcipriani@wikimedia.org> 1458604120 -0700\ncommitter Tyler Cipriani <tcipriani@wikimedia.org> 1458604120 -0700\n\nInitial Commit
Parts of that message are obvious: author
and committer
obviously come from my .gitconfig
. There is a Unix epoch timestamp with a timezone offset. commit
is the type of object. 195
is the byte-length of the remainder of the message.
There are a few parts of that message that aren’t immediately obvious. What is tree 098e6de29daf4e55f83406b49f5768df9bc7d624
? And why would we store this message in .git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da
and not .git/objects/commit-message
? Is a merkle what I think it is? The answer to all of these questions and many more is the same: Cryptographic Hash Functions.
A cryptographic hash function is a function that when given an input of any length it creates a fixed-length output. Furthermore (and more importantly), the fixed-length output should be unique to a given input; any change in input will likely cause a big change in the output. Git uses a cryptographic hash function called Secure Hash Algorithm 1 (SHA-1).
\nYou can play with the SHA-1 function on the command line:
\n$ echo 'message' | sha1sum\n1133e3acf0a4cbb9d8b3bfd3f227731b8cd2650b -\n$ echo 'message' | sha1sum\n1133e3acf0a4cbb9d8b3bfd3f227731b8cd2650b -\n$ echo 'message1' | sha1sum\nc133514a60a4641b83b365d3dc7b715dc954e010 -
Note the big change in the output of sha1sum
from a tiny change in input. This is what cryptographic hash functions do.
Now that we have some idea of what is inside a commit object, let’s reverse-engineer the commit object from the HEAD
of our merkle
repo:
$ python2 -c 'import sys,zlib; \\\nprint zlib.decompress(sys.stdin.read());' \\\n< .git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da | \\\nod -c\n 0000000 c o m m i t 1 9 5 \\0 t r e e \n 0000020 0 9 8 e 6 d e 2 9 d a f 4 e 5 5\n 0000040 f 8 3 4 0 6 b 4 9 f 5 7 6 8 d f\n 0000060 9 b c 7 d 6 2 4 \\n a u t h o r \n 0000100 T y l e r C i p r i a n i <\n 0000120 t c i p r i a n i @ w i k i m e\n 0000140 d i a . o r g > 1 4 5 8 6 0 4\n 0000160 1 2 0 - 0 7 0 0 \\n c o m m i t\n 0000200 t e r T y l e r C i p r i a\n 0000220 n i < t c i p r i a n i @ w i\n 0000240 k i m e d i a . o r g > 1 4 5\n 0000260 8 6 0 4 1 2 0 - 0 7 0 0 \\n \\n I\n 0000300 n i t i a l C o m m i t \\n \\n\n 0000317
$ printf 'tree 098e6de29daf4e55f83406b49f5768df9bc7d62k4\\n' >> commit-msg\n$ printf 'author Tyler Cipriani <tcipriani@wikimedia.org> 1458604120 -0700\\n' >> commit-msg\n$ printf 'committer Tyler Cipriani <tcipriani@wikimedia.org> 1458604120 -0700\\n' >> commit-msg\n$ printf '\\nInitial Commit\\n' >> commit-msg
$ sha1sum <(cat \\\n <(printf "commit ") \\\n <(wc -c < commit-msg | tr -d '\\n') \\\n <(printf '%b' '\\0') commit-msg)\n1a06ce381ac14f7a5baa1670691c2ff8a73aa6da /dev/fd/63
Hmm… that seems familiar
\n$ export COMMIT_HASH=$(sha1sum <(cat <(printf "commit ") <(wc -c < commit-msg | tr -d '\\n') <(printf '%b' '\\0') commit-msg) | cut -d' ' -f1)\n$ find ".git/objects/${COMMIT_HASH:0:2}" -type f -name "${COMMIT_HASH:(-38)}"\n.git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da
The commit object is a zlib-compressed, formatted message that is stored in a file named after the SHA-1 hash of the file’s un-zlib
compressed contents.
(/me wipes brow)
\nLet’s use git-cat-file
to see if we can explore the tree 098e6de29daf4e55f83406b49f5768df9bc7d62k4
-part of the commit message object:
$ git cat-file -p 1a06ce381ac14f7a5baa1670691c2ff8a73aa6da\ntree 098e6de29daf4e55f83406b49f5768df9bc7d624\nauthor Tyler Cipriani <tcipriani@wikimedia.org> 1458604120 -0700\ncommitter Tyler Cipriani <tcipriani@wikimedia.org> 1458604120 -0700
$ git cat-file -p 098e6de29daf4e55f83406b49f5768df9bc7d624\n100644 blob 1b9f426a8407ffee551ad2993c5d7d3780296353 README
Hey that’s the text I put into README
!
So .git/HEAD
refers to .git/refs/heads/master
, calling git-cat-file
on the object found inside that file shows that it’s the commit object we recreated. The commit object points to 098e6de29daf4e55f83406b49f5768df9bc7d624
, which is a tree object with the contents: 100644 blob 1b9f426a8407ffee551ad2993c5d7d3780296353 README
The blob
object 1b9f426a8407ffee551ad2993c5d7d3780296353
is the contents of README
! So it seems each commit
object points to a tree
object that points to other objects.
Let’s see if we can paste together what Git is doing at a low-level when we make a new commit:
\nREADME
, hash the contents using SHA-1, and store as a blob
object in .git/objects
.tree
in .git/objects
..gitconfig
and the hash of the top-level tree. Hash this information and store it as a commit
object in .git/objects
.It seems that there may be a chain of dependencies:
\nvar gitDag = {\n // blob (add .b for blob)\n '1b9f426a8407ffee551ad2993c5d7d3780296353.b': [],\n // tree (.t == tree) is a hash that includes the hash from blob\n '098e6de29daf4e55f83406b49f5768df9bc7d624.t': ['1b9f426a8407ffee551ad2993c5d7d3780296353.b'],\n // commit (.c == commit) is a hash that includes the hash from tree\n '1a06ce381ac14f7a5baa1670691c2ff8a73aa6da.c': ['098e6de29daf4e55f83406b49f5768df9bc7d624.t'],\n};\n\nmakeGraph('.merkle-2', forceFormat(gitDag));
You’d be forgiven for thinking that is a line.
\nWhat’s really happening is that there is a commit
object (1a06ce38
) that depends on a tree
object (098e6de2
) that depends on a blob
(1b9f426a
).
Since it’s running each of these objects through a hash function and each of them contains a reference up the chain of dependencies, a minor change to either the blob
or the tree
will create a drastically different commit
object.
Applying a cryptographic hash function on top of a graph was Ralph Merkle’s big idea. This scheme makes magic possible. Transferring verifiable and trusted information through an untrusted medium is toatz for realz possible with Ralph’s little scheme.
\nThe idea is that if you have the root-node hash, that is, the cryptographic hash of the node that depends on all other nodes (the commit
object in Git), and you obtained that root-node hash from a trusted source, you can trust all sub-nodes that stem from that root node if the hash of all those sub-root-nodes matches the root-node hash!
This is the mechanism by which things like Git, IPFS, Bitcoin, and BitTorrent are made possible: changing any one node in the graph changes all nodes that depend on that node all the way to the root-node (the commit
in Git).
I wrote a simple NodeJS script that creates a graph that is suitable for input into the JavaScript that I’ve already written that will create a D3.js force graph with whatever it finds in .git/objects
.
#!/usr/bin/env nodejs\n/* makeDag - creates a JSON dependency graph from .git/objects */\n\nvar glob = require('glob'),\n fs = require('fs'),\n zlib = require('zlib');\n\nvar types = ['tree', 'commit', 'blob'],\n treeRegex = {\n // 100644 README\\0[20 byte sha1]\n regex: /[0-9]+\\s[^\\0]+\\0((.|\\n){20})/gm,\n fn: function(sha) {\n var buf = new Buffer(sha[1], 'binary');\n return buf.toString('hex') + '.b';\n }\n },\n commitRegex = {\n // tree 098e6de29daf4e55f83406b49f5768df9bc7d624\n regex: /(tree|parent)\\s([a-f0-9]{40})/gm,\n fn: function(sha) {\n if (sha[1] === 'tree') {\n return sha[2] + '.t';\n }\n return sha[2] + '.c';\n }\n },\n total = 0,\n final = {};\n\n// determine file type, parse out SHA1s\nvar handleObjects = function(objData, name) {\n types.forEach(function(type) {\n var re, regex, match, key;\n\n if (!objData.startsWith(type)) { return; }\n\n key = name + '.' + type[0];\n final[key] = [];\n if (type === 'tree') { objType = treeRegex; }\n if (type === 'commit') { objType = commitRegex; }\n if (type === 'blob') { return; }\n\n // Remove the object-type and size from file\n objData = objData.split('\\0');\n objData.shift();\n objData = objData.join('\\0');\n\n // Recursive regex match remainder\n while ((match = objType.regex.exec(objData)) !== null) {\n final[key].push(objType.fn(match));\n }\n });\n\n // Don't output until you've got it all\n if (Object.keys(final).length !== total) {\n return;\n }\n\n // Output what ya got.\n console.log(final);\n};\n\n// Readable object names not file names\nvar getName = function(file) {\n var fileParts = file.split('/'),\n len = fileParts.length;\n return fileParts[len - 2] + fileParts[len - 1];\n}\n\n// Inflate the deflated git object file\nvar handleFile = function(file, out) {\n var name = getName(file);\n\n fs.readFile(file, function(e, data) {\n zlib.inflate(data, function(e, data) {\n if (e) { console.log(file, e); return; }\n handleObjects(data.toString('binary'), name);\n });\n });\n};\n\n// Sort through the gitobjects directory\nvar handleFiles = function(files) {\n files.forEach(function(file) {\n fs.stat(file, function(e, f) {\n if (e) { return; }\n if (f.isFile()) {\n // Don't worry about pack files for now\n if (file.indexOf('pack') > -1) { return; }\n total++;\n handleFile(file);\n }\n });\n\n });\n};\n\n(function() {\n glob('.git/objects/**/*', function(e, f) {\n if (e) { throw e; }\n handleFiles(f);\n });\n})();
Merkle graph transformations are often difficult to describe, but easy to see. Using this last piece of code to create and view graphs for several repositories has been illuminating. The graph visualization both illuminates and challenges my understanding of Git in ways I didn’t anticipate.
\nWhen you change your commit message, what happens to the graph? What depends on a commit? Where is the context for a commit?
\n$ git commit --amend -m 'This is the commit message now'\n[master 585448a] This is the commit message now\n Date: Mon Mar 21 16:48:40 2016 -0700\n 1 file changed, 1 insertion(+)\n create mode 100644 README\n$ find .git/objects -type f\n.git/objects/1b/9f426a8407ffee551ad2993c5d7d3780296353\n.git/objects/09/8e6de29daf4e55f83406b49f5768df9bc7d624\n.git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da\n.git/objects/da/94af3a21ac7e0c875bbbe6162aa1d26d699c73
Now the DAG is a bit different:
\nvar gitDag = { '098e6de29daf4e55f83406b49f5768df9bc7d624.t': [ '1b9f426a8407ffee551ad2993c5d7d3780296353.b' ],\n '1a06ce381ac14f7a5baa1670691c2ff8a73aa6da.c': [ '098e6de29daf4e55f83406b49f5768df9bc7d624.t' ],\n '1b9f426a8407ffee551ad2993c5d7d3780296353.b': [],\n 'da94af3a21ac7e0c875bbbe6162aa1d26d699c73.c': [ '098e6de29daf4e55f83406b49f5768df9bc7d624.t' ] }\n\nmakeGraph('.merkle-3', forceFormat(gitDag));
Here we see that there are now two commit
objects (1a06ce38
and da94af3a
) that both depend on a single tree
object (098e6de2
) that depends on a single blob
(1b9f426a
).
One of these commit objects will never be seen with git log
.
TIL: Git creates blob
objects as soon as a file is added to the staging area.
$ echo 'staged' > staged\n$ find .git/objects -type f\n.git/objects/1b/9f426a8407ffee551ad2993c5d7d3780296353\n.git/objects/09/8e6de29daf4e55f83406b49f5768df9bc7d624\n.git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da\n.git/objects/da/94af3a21ac7e0c875bbbe6162aa1d26d699c73
Notice that nothing depends on this object just yet. It’s a lonely orphan blob
.
$ git add staged\n$ find .git/objects -type f\n.git/objects/1b/9f426a8407ffee551ad2993c5d7d3780296353\n.git/objects/09/8e6de29daf4e55f83406b49f5768df9bc7d624\n.git/objects/1a/06ce381ac14f7a5baa1670691c2ff8a73aa6da\n.git/objects/da/94af3a21ac7e0c875bbbe6162aa1d26d699c73\n.git/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481\n$ makeDag\n{ '098e6de29daf4e55f83406b49f5768df9bc7d624.t': [ '1b9f426a8407ffee551ad2993c5d7d3780296353.b' ],\n '19d9cc8584ac2c7dcf57d2680375e80f099dc481.b': [],\n '1a06ce381ac14f7a5baa1670691c2ff8a73aa6da.c': [ '098e6de29daf4e55f83406b49f5768df9bc7d624.t' ],\n 'da94af3a21ac7e0c875bbbe6162aa1d26d699c73.c': [ '098e6de29daf4e55f83406b49f5768df9bc7d624.t' ],\n '1b9f426a8407ffee551ad2993c5d7d3780296353.b': [] }
Even unstaging and deleting the file doesn’t remove the object. Orphan objects in git are only garbage collected as part of git gc --prune
.
When this object is committed to the repo, it creates a whole new layer of the graph:
\n$ git commit -m 'Add staged file'\n[master 4f407b3] Add staged file\n 1 file changed, 1 insertion(+)\n create mode 100644 staged\n$ makeDag\n{ '098e6de29daf4e55f83406b49f5768df9bc7d624.t': [ '1b9f426a8407ffee551ad2993c5d7d3780296353.b' ],\n '19d9cc8584ac2c7dcf57d2680375e80f099dc481.b': [],\n '1a06ce381ac14f7a5baa1670691c2ff8a73aa6da.c': [ '098e6de29daf4e55f83406b49f5768df9bc7d624.t' ],\n '1b9f426a8407ffee551ad2993c5d7d3780296353.b': [],\n '4f407b396e6ecbb65de6cf192259c18ecd4d1e9b.c': \n [ '7ce38101e91de29ee0fee3aa9940cc81159e0f8d.t',\n 'da94af3a21ac7e0c875bbbe6162aa1d26d699c73.c' ],\n '7ce38101e91de29ee0fee3aa9940cc81159e0f8d.t': \n [ '1b9f426a8407ffee551ad2993c5d7d3780296353.b',\n '19d9cc8584ac2c7dcf57d2680375e80f099dc481.b' ],\n 'da94af3a21ac7e0c875bbbe6162aa1d26d699c73.c': [ '098e6de29daf4e55f83406b49f5768df9bc7d624.t' ] }
So we’ve created a new commit (4f407b39
) that is the parent of a different commit (da94af3a
) and a new tree (7ce38101
) that contains our old README
blob (1b9f426a
) and our new, previously orphaned, blob (19d9cc85
).
I’ve always enjoyed the idea that software (and computer science more generally) is nothing but an abstraction to manage complexity. Good software— powerful software—like Git—is a software that manages an incredible amount of complexity and hides it completely from the user.
\nIn recognition of this idea, I’ll leave you with the graph of my local copy of clippy—a small command line tool I created that is like man(1)
except it shows Clippy at the end of the man
output (yes, it’s dumb).
This should give you an idea of the complexity that is abstracted by the Git merkle graph: this repo contains 5 commits!
\nI find favicons pretty useful overall. That weird little Earthworm-Jim-esque bug-head in my bookmarks bar immediately queues me in on where to click to get to bugzilla. My site hasn’t had a favicon. The only explanation I can offer is laziness mixed with a lack of inspiration. A favicon isn’t typically a glamorous undertaking—it’s just one of those things that ought to be done.
\nMy first idea for a favicon was Eric S. Raymond’s Hacker Emblem. The Hacker Emblem is certainly a meaningful symbol, but it’s also kind of a cop-out.
\nI tried something with nyan cat and the hacker emblem, which was a solid idea, but sort of lost something at 16px×16px. Then I started to think, why not just have Conway’s Game of Life running in the favicon?
\nConway’s Game of Life is a zero player game with 4 simple rules (this is verbatim from the Wikipedia article):
\n\n\n\n
\n- Any live cell with less than two live neighbours dies, as if caused by under-population.
\n- Any live cell with two or three live neighbours lives on to the next generation.
\n- Any live cell with more than three live neighbours dies, as if by overcrowding.
\n- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
\n
My game of life is heavily derived from an IBM DeveloperWorks post, Conway’s Game of Life in CoffeeScript and canvas, except that I just used plain ol’ JavaScript, a much smaller grid, and I included some functionality to reset the game seed if the last two evolutions of life don’t change the grid layout.
\nAfter creating the game loop in JavaScript, I had to create some code to dynamically update the favicon.
\nFirst, I created the default favicon for this site by rendering out a glider via JavaScript and rendering the canvas as a png on the page.
\nGameOfLife.prototype.glider = [\n {x: 0, y: 1}\n ,{x: 1, y: 2}\n ,{x: 2, y: 0}\n ,{x: 2, y: 1}\n ,{x: 2, y: 2}\n]\n\n/* ... */\n\n/**\n * Seed with default of glider\n */\nGameOfLife.prototype.seed = function() {\n var i, j, rowLen, colLen, row, col, gliderLen\n\n // Start with all empty\n for(i = row = 0, rowLen = this.numberOfRows; i < rowLen; row = ++i) {\n this.curCellGen[row] = []\n for(j = col = 0, colLen = this.numberOfCols; j < colLen; col = ++j) {\n this.curCellGen[row][col] = this.createCell(false, row, col)\n }\n }\n\n // Create glider\n for(i = 0, gliderLen = this.glider.length; i < gliderLen; i++) {\n var x, y\n\n x = this.glider[i].x\n y = this.glider[i].y\n\n this.curCellGen[x][y] = this.createCell(true, x, y)\n }\n}\n\nGameOfLife.prototype.drawGrid = function() {\n var img = document.createElement('img')\n img.src = this.canvas.toDataURL('img/png')\n}
After rendering as a png in the browser, I saved the .png
to my computer. Then I uploaded the png to favicon-generator.org saving the resulting .ico
to my site’s directory root as favicon.ico
. favicon.ico
is what gets rendered in IE, since IE has found brand new ways to be non-compliant with emerging standards (le sigh).
The finishing touch is to make the GameOfLife.prototype.drawGrid
function update my favicon’s href
attribute on every tick
function call:
GameOfLife.prototype.createCanvas = function() {\n this.canvas = document.createElement('canvas')\n this.favicon = document.createElement('link')\n this.favicon.rel = 'icon'\n document.getElementsByTagName('head')[0].appendChild(link)\n}\n\n\nGameOfLife.prototype.drawGrid = function() {\n /* ... */\n this.favicon.href = this.canvas.toDataURL('img/png')\n}
The entirety of this javascript is available under the GPLv3 License on my github.
\nI recently contributed a fix to the bootstrap framework that detects on-screen scrollbars to determine whether or not the body should be padded when a modal window is opened to prevent shifting of background contents. Detecting on-screen scrollbars turned out to be a bit more involved than I initially anticipated.
\nThe tl;dr, semi-näive version:
\n\nThis works for most browsers. Basically it checks to see if the width of the window
element (read: including scrollbars) is greater than the width of the root element of the page (read: without scrollbars). If the width of the page with scrollbars is greater than the width of a page without scrollbars it would stand to reason that the extra width is a scrollbar.
This solution behaves correctly when IE10+ has @-ms-viewport { width: device-width; }
set (as it is in the bootstrap framework), which seems to result in scrollbars being auto-hidden. This solution also works for Chrome on the Mac where the scrollbars are automagically hidden.
This certainly seems to function as expected for IE9+; however, IE8 is our newest anchor browser so IE8 should be addressed in any ostensibly “cross-browser” approaches.
\nwindow.innerWidth
doesn’t exist on IE8. Any workarounds you see utilizing document.documentElement
will not include scrollbars in the reported width, so document.docutmentElement
will not be an adequate substitute in < IE9.
One thing to check is the scrollHeight
. If the scrollHeight
of the root element is greater than the clientHeight
of the root element, then that root element is going to need to scroll to show the overflowing content:
var hasScrollbar\n\nif (typeof window.innerWidth === 'number')\n hasScrollbar = window.innerWidth > document.documentElement.clientWidth\n\nhasScrollbar = hasScrollbar ||\n document.documentElement.scrollHeight > document.documentElement.clientHeight
Again, this is an oversimplification. The overflow
property of the root element can modify the appearance of scrollbars (to create on-screen faux llbars). Of course, once again, IE and modern browsers differ about how they’ve implemented the javascript api for accessing element styles. We can account for this difference and grab the overflow property like this:
var overflowStyle\n\nif (typeof document.documentElement.currentStyle !== 'undefined')\n overflowStyle = document.documentElement.currentStyle.overflow\n\noverflowStyle = overflowStyle || window.getComputedStyle(document.documentElement, '').overflow
The two values of the overflow
or overflow-y
properties that will create scrollbars are visible
and auto
provided that the scrollHeight
is greater than the clientHeight
. A value of scroll
for the overflow
or overflow-y
properties will always cause a scrollbar.
This is, once again, a bit of a simplification.
\nIn quirksmode in IE8 document.documentElement.clientHeight
is 0. The root element is document.body
. This won’t affect most people reading this, but just to be on the safe side let’s add it into our solution.
The final solution looks like this:
\nvar hasScrollbar = function() {\n // The Modern solution\n if (typeof window.innerWidth === 'number')\n return window.innerWidth > document.documentElement.clientWidth\n\n // rootElem for quirksmode\n var rootElem = document.documentElement || document.body\n\n // Check overflow style property on body for fauxscrollbars\n var overflowStyle\n\n if (typeof rootElem.currentStyle !== 'undefined')\n overflowStyle = rootElem.currentStyle.overflow\n\n overflowStyle = overflowStyle || window.getComputedStyle(rootElem, '').overflow\n\n // Also need to check the Y axis overflow\n var overflowYStyle\n\n if (typeof rootElem.currentStyle !== 'undefined')\n overflowYStyle = rootElem.currentStyle.overflowY\n\n overflowYStyle = overflowYStyle || window.getComputedStyle(rootElem, '').overflowY\n\n var contentOverflows = rootElem.scrollHeight > rootElem.clientHeight\n var overflowShown = /^(visible|auto)$/.test(overflowStyle) || /^(visible|auto)$/.test(overflowYStyle)\n var alwaysShowScroll = overflowStyle === 'scroll' || overflowYStyle === 'scroll'\n\n return (contentOverflows && overflowShown) || (alwaysShowScroll)\n}
If I missed something, or if this solution is a bit of an oversimplification (le sigh), please let me know in the comments.
\n" } ] }