Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is readable source code available for google's bouncy ball doodle?

If you haven't seen the current google doodle, you should check it out. Basically, it's a bunch of balls that fly from the mouse.

Google doodle
(source: dailymail.co.uk)

Its coded differently in IE to other browsers. In IE, it's done with . characters, whereas other browsers use border-radius.

I've found the source code within the page (included below for completeness, after beautification), but I was wondering if google have put their un-compressed source code up on Google code, or a public repository.

Anyone know where I'd find a human-readable copy of the Javascript used on this page?


(function () {
    try {
        if (!google.doodle) google.doodle = {};
        var a = 200,
            g = -200,
            j = -200,
            k, l, m, n = 0,
            o = 0,
            p = 0,
            q = 35,
            r, s = [],
            t, u, v;
        google.doodle.init = function () {
            if (!v && window.location.href.indexOf("#") == -1) {
                v = true;
                if (t = document.getElementById("hplogo")) {
                    google.j && google.j.en && w(100, x, function () {
                        return google.rein && google.dstr
                    });
                    w(100, y, function () {
                        return google.listen
                    });
                    w(100, z, function () {
                        return google.browser
                    })
                }
            }
        };
        var w = function (b, c, d) {
            if (d()) c();
            else b < 200 && window.setTimeout(function () {
                w(b + 1, c, d)
            }, b)
        },
            x = function () {
                if (!google.doodle.n) {
                    google.doodle.n = true;
                    google.rein.push(google.doodle.init);
                    google.dstr.push(A)
                }
            },
            y = function () {
                google.listen(document, "mousemove", B)
            },
            B = function (b) {
                a = 200;
                g = b.clientX - t.offsetLeft;
                j = b.clientY - t.offsetTop
            },
            C = function () {
                return [u ? window.screenLeft : window.screenX, u ? window.screenTop : window.screenY, document.body.clientWidth]
            },
            z = function () {
                u = google.browser.engine.IE && google.browser.engine.version != "9.0";
                s = [D(202, 78, 9, "ed9d33"), D(348, 83, 9, "d44d61"), D(256, 69, 9, "4f7af2"), D(214, 59, 9, "ef9a1e"), D(265, 36, 9, "4976f3"), D(300, 78, 9, "269230"), D(294, 59, 9, "1f9e2c"), D(45, 88, 9, "1c48dd"), D(268, 52, 9, "2a56ea"), D(73, 83, 9, "3355d8"), D(294, 6, 9, "36b641"), D(235, 62, 9, "2e5def"), D(353, 42, 8, "d53747"), D(336, 52, 8, "eb676f"), D(208, 41, 8, "f9b125"), D(321, 70, 8, "de3646"), D(8, 60, 8, "2a59f0"), D(180, 81, 8, "eb9c31"), D(146, 65, 8, "c41731"), D(145, 49, 8, "d82038"), D(246, 34, 8, "5f8af8"), D(169, 69, 8, "efa11e"), D(273, 99, 8, "2e55e2"), D(248, 120, 8, "4167e4"), D(294, 41, 8, "0b991a"), D(267, 114, 8, "4869e3"), D(78, 67, 8, "3059e3"), D(294, 23, 8, "10a11d"), D(117, 83, 8, "cf4055"), D(137, 80, 8, "cd4359"), D(14, 71, 8, "2855ea"), D(331, 80, 8, "ca273c"), D(25, 82, 8, "2650e1"), D(233, 46, 8, "4a7bf9"), D(73, 13, 8, "3d65e7"), D(327, 35, 6, "f47875"), D(319, 46, 6, "f36764"), D(256, 81, 6, "1d4eeb"), D(244, 88, 6, "698bf1"), D(194, 32, 6, "fac652"), D(97, 56, 6, "ee5257"), D(105, 75, 6, "cf2a3f"), D(42, 4, 6, "5681f5"), D(10, 27, 6, "4577f6"), D(166, 55, 6, "f7b326"), D(266, 88, 6, "2b58e8"), D(178, 34, 6, "facb5e"), D(100, 65, 6, "e02e3d"), D(343, 32, 6, "f16d6f"), D(59, 5, 6, "507bf2"), D(27, 9, 6, "5683f7"), D(233, 116, 6, "3158e2"), D(123, 32, 6, "f0696c"), D(6, 38, 6, "3769f6"), D(63, 62, 6, "6084ef"), D(6, 49, 6, "2a5cf4"), D(108, 36, 6, "f4716e"), D(169, 43, 6, "f8c247"), D(137, 37, 6, "e74653"), D(318, 58, 6, "ec4147"), D(226, 100, 5, "4876f1"), D(101, 46, 5, "ef5c5c"), D(226, 108, 5, "2552ea"), D(17, 17, 5, "4779f7"), D(232, 93, 5, "4b78f1")];
                var b = C();
                k = b[0];
                l = b[1];
                m = b[2];
                E()
            },
            A = function () {
                google.unlisten(document, "mousemove", B);
                r && window.clearTimeout(r);
                if (s) for (var b = 0, c; c = s[b++];) c.m()
            },
            E = function () {
                var b = C(),
                    c = b[0],
                    d = b[1];
                b = b[2];
                n = c - k;
                o = d - l;
                p = b - m;
                k = c;
                l = d;
                m = b;
                a = Math.max(0, a - 2);
                c = true;
                for (d = 0; b = s[d++];) {
                    b.update();
                    if (c) c = b.i
                }
                q = c ? 250 : 35;
                r = window.setTimeout(E, q)
            },
            D = function (b, c, d, h) {
                return new F(b, c, d, h)
            },
            F = function (b, c, d, h) {
                this.x = this.o = b;
                this.y = this.p = c;
                this.k = this.h = d;
                b = 100;
                this.a = b * (Math.random() - 0.5);
                this.c = b * (Math.random() - 0.5);
                this.l = 3 + Math.random() * 98;
                this.r = 0.1 + Math.random() * 0.4;
                this.e = 0;
                this.g = 1;
                this.i = false;
                this.d = document.createElement("div");
                this.d.className = "particle";
                this.style = this.d.style;
                h = "#" + h;
                if (u) {
                    this.d.innerHTML = ".";
                    this.style.fontFamily = "Monospace";
                    this.style.color = h;
                    this.style.fontSize = "100px";
                    this.style.lineHeight = 0;
                    this.style.cursor = "default"
                } else {
                    this.d.className += " circle";
                    this.style.backgroundColor = h
                }
                t.appendChild(this.d);
                this.m = function () {
                    t.removeChild(this.d)
                };
                this.update = function () {
                    this.x += this.a;
                    this.y += this.c;
                    this.a = Math.min(50, Math.max(-50, (this.a + (n + p) / this.h) * 0.92));
                    this.c = Math.min(50, Math.max(-50, (this.c + o / this.h) * 0.92));
                    var e = g - this.x,
                        f = j - this.y,
                        i = Math.sqrt(e * e + f * f);
                    e /= i;
                    f /= i;
                    if (i < a) {
                        this.a -= e * this.l;
                        this.c -= f * this.l;
                        this.e += (0.005 - this.e) * 0.4;
                        this.g = Math.max(0, this.g * 0.9 - 0.01);
                        this.a *= 1 - this.g;
                        this.c *= 1 - this.g
                    } else {
                        this.e += (this.r - this.e) * 0.005;
                        this.g = Math.min(1, this.g + 0.03)
                    }
                    e = this.o - this.x;
                    f = this.p - this.y;
                    i = Math.sqrt(e * e + f * f);
                    this.a += e * this.e;
                    this.c += f * this.e;
                    this.k = this.h + i / 8;
                    this.i = i < 0.3 && this.a < 0.3 && this.c < 0.3;
                    if (!this.i) {
                        if (!u) this.style.width = this.style.height = this.k * 2 + "px";
                        this.style.left = this.x - (u ? 20 : 0) + "px";
                        this.style.top = this.y + "px"
                    }
                }
            }
    } catch (G) {
        google.ml(G, false, {
            _sn: "PAR"
        })
    };
})();
google.doodle.init()
like image 795
Eric Avatar asked Sep 07 '10 16:09

Eric


2 Answers

I've found a recreation of the bouncy balls here, but the implementation is slighty different. Source code is available though.

like image 74
Eric Avatar answered Oct 16 '22 10:10

Eric


Deobfuscated

For the curious, here's a human readable version of the above code. I haven't tested it yet but it should be mostly correct.

Note: to improve readability I've only included stuff in the try block (so it isn't indented twice unnecessarily).

Further Note: Most of the interesting stuff is in the constructor function named Dot (search for 'Dot =').

Code

if (!google.doodle) google.doodle = {};
var interactionDistance = 200,
    mouseX = -200,
    mouseY = -200,
    // position of window on previous update
    lastWindowLeft, lastWindowTop, lastWindowWidth,
    // how much the window has changed since last update
    windowDeltaX = 0, 
    windowDeltaY = 0, 
    windowDeltaW = 0, // width
    updateInterval = 35,
    updateTimerID, dotArray = [],
    dotContainer, isIE, started;
google.doodle.init = function () {
    if (!started && window.location.href.indexOf("#") == -1) {
        started = true;
        if (dotContainer = document.getElementById("hplogo")) {
            if(google.j && google.j.en){
                waitUntilCondition(100, googleInit, function () {
                    return google.rein && google.dstr
                });
            }
            waitUntilCondition(100, registerMouseMoveListener, function () {
                return google.listen
            });
            waitUntilCondition(100, start, function () {
                return google.browser
            })
        }
    }
};

// wait awhile for condition to be true. if it doesn't happen after a
// while, give up.
var waitUntilCondition = function (interval, action, condition) {
        if (condition()) action();

        else if(interval < 200){
            window.setTimeout(function () {
                waitUntilCondition(interval + 1, action, condition)
            }, interval);
        }
    },
    googleInit = function () {
        if (!google.doodle.n) {
            google.doodle.n = true;
            google.rein.push(google.doodle.init);
            google.dstr.push(stop)
        }
    },
    registerMouseMoveListener = function () {
        google.listen(document, "mousemove", mouseMoveListener)
    },
    mouseMoveListener = function (eventObject) {
        // reset the interaction distance
        interactionDistance = 200;

        // figure out where the mouse is
        mouseX = eventObject.clientX - dotContainer.offsetLeft;
        mouseY = eventObject.clientY - dotContainer.offsetTop
    },
    // array of [left, top, width] (cross browser)
    windowInfo = function () {
        return [isIE ? window.screenLeft : window.screenX,
                isIE ? window.screenTop : window.screenY,
                document.body.clientWidth]
    },
    start = function () {

        // check for Internet Ruiner
        isIE = google.browser.engine.IE &&
               google.browser.engine.version != "9.0";

        // make the dots
        dotArray = [createDot(202, 78, 9, "ed9d33"), createDot(348, 83, 9, "d44d61"), createDot(256, 69, 9, "4f7af2"), createDot(214, 59, 9, "ef9a1e"), createDot(265, 36, 9, "4976f3"), createDot(300, 78, 9, "269230"), createDot(294, 59, 9, "1f9e2c"), createDot(45, 88, 9, "1c48dd"), createDot(268, 52, 9, "2a56ea"), createDot(73, 83, 9, "3355d8"), createDot(294, 6, 9, "36b641"), createDot(235, 62, 9, "2e5def"), createDot(353, 42, 8, "d53747"), createDot(336, 52, 8, "eb676f"), createDot(208, 41, 8, "f9b125"), createDot(321, 70, 8, "de3646"), createDot(8, 60, 8, "2a59f0"), createDot(180, 81, 8, "eb9c31"), createDot(146, 65, 8, "c41731"), createDot(145, 49, 8, "d82038"), createDot(246, 34, 8, "5f8af8"), createDot(169, 69, 8, "efa11e"), createDot(273, 99, 8, "2e55e2"), createDot(248, 120, 8, "4167e4"), createDot(294, 41, 8, "0b991a"), createDot(267, 114, 8, "4869e3"), createDot(78, 67, 8, "3059e3"), createDot(294, 23, 8, "10a11d"), createDot(117, 83, 8, "cf4055"), createDot(137, 80, 8, "cd4359"), createDot(14, 71, 8, "2855ea"), createDot(331, 80, 8, "ca273c"), createDot(25, 82, 8, "2650e1"), createDot(233, 46, 8, "4a7bf9"), createDot(73, 13, 8, "3d65e7"), createDot(327, 35, 6, "f47875"), createDot(319, 46, 6, "f36764"), createDot(256, 81, 6, "1d4eeb"), createDot(244, 88, 6, "698bf1"), createDot(194, 32, 6, "fac652"), createDot(97, 56, 6, "ee5257"), createDot(105, 75, 6, "cf2a3f"), createDot(42, 4, 6, "5681f5"), createDot(10, 27, 6, "4577f6"), createDot(166, 55, 6, "f7b326"), createDot(266, 88, 6, "2b58e8"), createDot(178, 34, 6, "facb5e"), createDot(100, 65, 6, "e02e3d"), createDot(343, 32, 6, "f16d6f"), createDot(59, 5, 6, "507bf2"), createDot(27, 9, 6, "5683f7"), createDot(233, 116, 6, "3158e2"), createDot(123, 32, 6, "f0696c"), createDot(6, 38, 6, "3769f6"), createDot(63, 62, 6, "6084ef"), createDot(6, 49, 6, "2a5cf4"), createDot(108, 36, 6, "f4716e"), createDot(169, 43, 6, "f8c247"), createDot(137, 37, 6, "e74653"), createDot(318, 58, 6, "ec4147"), createDot(226, 100, 5, "4876f1"), createDot(101, 46, 5, "ef5c5c"), createDot(226, 108, 5, "2552ea"), createDot(17, 17, 5, "4779f7"), createDot(232, 93, 5, "4b78f1")];

        // get initial window positions
        var windowRect = windowInfo();
        lastWindowLeft = windowRect[0];
        lastWindowTop = windowRect[1];
        lastWindowWidth = windowRect[2];

        // start movin' the dots
        updateDots()
    },

    stop = function () {
        google.unlisten(document, "mousemove", mouseMoveListener);
        updateTimerID && window.clearTimeout(updateTimerID);

        if (dotArray){
            for (var b = 0, dot; dot = dotArray[b++];) dot.remove();   
        }
    },

    /* update the positions of all the dots then set a timer to call this
     * function again in a little while */
    updateDots = function () {
        var i,
            isLongInterval = true;
            windowRect = windowInfo(),
            windowLeft = windowRect[0],
            windowTop = windowRect[1],
            windowWidth = windowRect[2],
            dot;

        // figure how much the window has moved (for the shake effect)
        windowDeltaX = windowLeft - lastWindowLeft;
        windowDeltaY = windowTop - lastWindowTop;
        windowDeltaW = windowWidth - lastWindowWidth;

        lastWindowLeft = windowLeft;
        lastWindowTop = windowTop;
        lastWindowWidth = windowWidth;

        // mouse interaction distance decays when you don't move the mouse
        interactionDistance = Math.max(0, interactionDistance - 2);

        for (i = 0; dot = dotArray[i++];) {
            // move the dots
            dot.update();
            // longer timer interval if all dots are stationary
            isLongInterval = isLongInterval && dot.isStationary;
        }
        updateInterval = isLongInterval ? 250 : 35;

        updateTimerID = window.setTimeout(updateDots, updateInterval)
    },
    createDot = function (x0, y0, radius, color) {
        return new Dot(x0, y0, radius, color)
    },
    Dot = function (x0, y0, radius, color) {
        this.x = this.x0 = x0;
        this.y = this.y0 = y0;
        this.dotRadius = this.baseDotRadius = radius;

        // move us some random amount away from our start position, so we
        // look cool when the page loads
        var b = 100;
        this.xDelta = b * (Math.random() - 0.5);
        this.yDelta = b * (Math.random() - 0.5);

        // how strongly do we move away from the cursor?
        // (between 3 and 100)
        this.repulsion = 3 + Math.random() * 98;
        // how strongly do we try to move toward our rest position? 
        // (between 0.1 and 0.5)
        this.springiness = 0.1 + Math.random() * 0.4;

        // how much is our spring pulling on us right now?
        this.springPull = 0;
        // how unlikely is this dot to move away from the cursor right now?
        // (between 0 and 1)
        this.laziness = 1;

        this.isStationary = false;
        this.particle = document.createElement("div");
        this.particle.className = "particle";
        this.style = this.particle.style;
        color = "#" + color;
        if (isIE) {
            this.particle.innerHTML = ".";
            this.style.fontFamily = "Monospace";
            this.style.color = color;
            this.style.fontSize = "100px";
            this.style.lineHeight = 0;
            this.style.cursor = "default"
        } else {
            this.particle.className += " circle";
            this.style.backgroundColor = color
        }
        dotContainer.appendChild(this.particle);
        this.remove = function () {
            dotContainer.removeChild(this.particle)
        };
        this.update = function () {
            // update position for this frame
            this.x += this.xDelta;
            this.y += this.yDelta;

            // figure out how much to move it next frame

            // base change is based on window movement
            var scaledWindowX = 
                    (windowDeltaX + windowDeltaW) / this.baseDotRadius,
                scaledWindowY = windowDeltaY / this.baseDotRadius;

            this.xDelta = Math.min(50, 
                    Math.max(-50, (this.xDelta + scaledWindowX) * 0.92));
            this.yDelta = Math.min(50, 
                    Math.max(-50, (this.yDelta + scaledWindowY) * 0.92));

                // how far away is the mouse?
            var xMouseDist = mouseX - this.x,
                yMouseDist = mouseY - this.y,
                mouseDistance = 
                    Math.sqrt(xMouseDist * xMouseDist + yMouseDist * yMouseDist),
                // how far away are we from our rest position?
                xDist = this.x0 - this.x,
                yDist = this.y0 - this.y,
                displacement = Math.sqrt(xDist * xDist + yDist * yDist);


            // normalize the mouse's relative position vector
            // (so we react with the same force as long as we're inside the
            // interactionDistance)
            xMouseDist /= mouseDistance;
            yMouseDist /= mouseDistance;

            // are we close to the mouse?
            if (mouseDistance < interactionDistance) { 
                // if so, bounce away from it
                this.xDelta -= xMouseDist * this.repulsion;
                this.yDelta -= yMouseDist * this.repulsion;

                // remove the spring pull when the mouse is close by
                // (approaches 0.005)
                this.springPull += (0.005 - this.springPull) * 0.4;

                // the more the mouse moves, the more responsive close by dots
                // become (laziness goes to zero)
                this.laziness = Math.max(0, this.laziness * 0.9 - 0.01);
                this.xDelta *= 1 - this.laziness;
                this.yDelta *= 1 - this.laziness;
            } else {
                // if not, get more springy and less responsive to the mouse

                // when the mouse isn't close, have the spring pull get slowly
                // closer to the springiness value
                this.springPull += 
                    (this.springiness - this.springPull) * 0.005;

                // when the mouse doesn't move for a while, dots become less
                // responsive (laziness goes to one)
                this.laziness = Math.min(1, this.laziness + 0.03);
            }

            // apply springPull
            this.xDelta += xDist * this.springPull;
            this.yDelta += yDist * this.springPull;

            // figure out how big the dot should be
            this.dotRadius = this.baseDotRadius + displacement / 8;

            // decide if we're moving
            this.isStationary = displacement < 0.3 && 
                                this.xDelta < 0.3 &&
                                this.yDelta < 0.3;

            if (!this.isStationary) {

                // if we aren't on IE, change the dot size
                if (!isIE) {
                    this.style.width =
                    this.style.height = this.dotRadius * 2 + "px";
                } 

                // move it
                this.style.left = this.x - (isIE ? 20 : 0) + "px";
                this.style.top = this.y + "px";
            }
        }
    }

Extras

There's a permanent page with this doodle here.

If I've messed something up, leave me a comment and I'll fix it.

like image 42
12 revs Avatar answered Oct 16 '22 09:10

12 revs