Sprite Animation with Parallax effect - through mootools



Change night and day, click here

This is just an experiment that will probably turn into the basis of my blog's header (once I add methods such as blowUpLemming(); and so forth). Although--in principle--the class used to support and run countless little lemmings, having added the parallax effect through the background grass and stars movement means it can only have the effect on one lemming at a time.

Changelog
24/11/2009 - Enabled blow-up lemming, fireworks effect based on work by crisp
24/11/2009 - Fixed IE7 support and made parallax effect smoother
24/11/2009 - Added options for size of container element / parallax layer
23/11/2009 - Added basic interaction with lemming and lemming 'chat'
23/11/2009 - Added support for daylight and night time themes, detected automatically as per client's PC clock
var Lemmings = new Class({
    Implements: [Options, Events],
    lemmings: [],
    fireworks: [],
    options: {
        spriteWidth: 32,
        spriteHeight: 32,
        spriteContainerWidth: 256,
        spriteExplodeContainerWidth: 512,
        drawSpeed: 50,
        marginTop: 52,
        spriteZindex: 1000,
        daytime: "auto",
        daylightDetect: true,
        parallaxLayerSize: "100%"
    },
    parallax: {},
    setTheme: function() {
        this.container.setStyles({
            overflow: "hidden",
            background: "url(http://fragged.org/images/parallax-bottom"+this.theme+".gif)"
        });


        this.parallax.topLayer.setStyles({
            background: "url(http://fragged.org/images/parallax-top"+this.theme+".gif) no-repeat -400px top"
        });

        if (this.themeNotice)
            this.themeNotice.dispose();

        this.themeNotice = new Element("div", {
            styles: {
                fontSize: "12px",
                padding: 5,
                fontWeight: "bold",
                zIndex: 100000,
                position: "relative",
                float: "right"
            },
            html: (this.theme == "D") ? "Daytime Lemmings" : "Lemmings in the Dark",
            opacity: 0
        }).injectTop(this.container).fade(1);

        (function() {
            this.themeNotice.fade(0);
        }).delay(2000, this);
    },
    initialize: function(el, options) {
        this.setOptions(options);
         // daytime / night time...
        var hours = new Date().get("hr");
        this.theme = (this.options.daytime == "auto") ? (hours > 8 && hours < 16) ? "D" : "" : this.options.daytime;

        this.container = $(el);
        this.container.addEvents({
            click: function() {
                return;
                this.addLemming("r", 0, 2);
            }.bind(this)
        }).setStyles({
            overflow: "hidden"
        });

        this.coords = this.container.getCoordinates();

        this.width = this.container.getSize().x;
        this.parallax.bottomLayer = 0;


        this.parallax.topLayer = new Element("div", {
            styles: {
                position: "absolute",
                width: this.options.parallaxLayerSize,
                height: 61,
                zIndex: 1
            }
        }).inject(this.container);

        /* this.parallax.fx = new Fx.Morph(this.parallax.topLayer, {
            transition: Fx.Transitions.Sine.easeInOut
        });
        */

        this.setTheme();


        window.addEvent("resize", function() {
            this.width = this.container.getSize().x;
        }.bind(this));

    },
    setSprite: function(lem) {
        var url = (lem.exploding)
            ? "http://fragged.org/wp-content/themes/envy/images/lemming_explode_s.gif"
            : "http://fragged.org/wp-content/themes/envy/images/lemming_walk_" +lem.direction+".gif"

        lem.el.setStyles({
            marginLeft: lem.pos,
            background: "url("+url+") no-repeat 0 0"
        });
    },
    addLemming: function(direction, pos, step) {
        var lem = {
            el: new Element("div", {
                    styles: {
                        backgroundRepeat: "no-repeat",
                        width: this.options.spriteWidth,
                        height: this.options.spriteHeight,
                        position: "absolute",
                        float: "left",
                        marginTop: this.options.marginTop,
                        zIndex: this.options.spriteZindex
                    }
            }),
            direction: direction || "r",
            pos: pos || 0,
            step: step || 1
        };

        lem.el.inject(this.container);

        lem.el.addEvents({
            mouseenter: function() {
                lem.el.store("step", lem.step);

                // experimental.
                /*
                lem.step++;
                lem.direction = lem.direction == "r" ? "l" : "r";
                this.setSprite(lem);
                this.lemmingChat(lem);
                return;*/
                // end experimental

                lem.step = 0;
                this.lemmingChat(lem);
            }.bind(this),
            mouseleave: function() {
                // experimental
                /*
                lem.step = lem.el.retrieve("step");
                var chat = lem.el.getFirst();
                chat.fade(0);
                return;
                */
                // end experimental

                lem.direction = lem.direction == "r" ? "l" : "r";
                this.setSprite(lem);
                lem.step = lem.el.retrieve("step");
                var chat = lem.el.getFirst();
                chat.fade(0);

            }.bind(this),
            click: function() {
                this.explodeLemming(lem);
            }.bind(this)
        });

        this.setSprite(lem);
        this.lemmings.push(lem);
    },
    lemmingChat: function(lem, what) {
        what = what || ["?","!", "#", "@", "no"].getRandom();
        lem.el.empty();
        new Element("div", {
            "class": "lemChat",
            html: "
"+what+"
", opacity: 0 }).inject(lem.el).fade(1); }, moveLemmings: function() { this.timer = this.drawWorld.periodical(this.options.drawSpeed, this); // this.startParallax(); }, explodeLemming: function(lem) { lem.exploding = true; this.setSprite(lem); lem.el.removeEvents(); (function() { lem.el.fade(0); }).delay(1000, this); return; (function() { this.addLemming("r", $random(0, this.width - this.options.spriteWidth), lem.step); }).delay(3000, this); }, firework: function(x, y) { this.num_firework++; var i = this.fireworks.length; var colors = ['#ffffff','#ffff00','#00ff00','#ff0000','#0000ff','#ff00ff','#00ffff'], fw = []; var j = 20, f, a, s; while(j--) { a = Math.random() * 6.294; s = (Math.random() >.6) ? 4 : Math.random() * 4; f = new Element("div", { "class": "spark", styles: { background: colors[Math.floor(Math.random()*7)], top: y, left: x }, y: y, x: x, dx: s * Math.sin(a), dy: s * Math.cos(a) - 4 }).inject(this.container); fw[j] = f; } this.fireworks.push(fw); this.running = false; this.runFirework(fw); }, runFirework: function(fw) { fw.each(function(f) { if ($type(f) !== "element") return; var y = f.get("y").toFloat(), x = f.get("x").toFloat(), dx = f.get("dx").toFloat(), dy = f.get("dy").toFloat(); // console.log(y, x, dy, dx); y += dy += 0.18; x += dx; f.set({ y: y, x: x }); var op = f.getStyle("opacity").toFloat() - .05; if (y < this.coords.top || y > this.coords.bottom || x < this.coords.left || x > this.coords.right || op <= 0) { // console.info("disposing of ", f, y); f.dispose(); fw.erase(f); } else { f.set({ styles: { top: y, left: x }, opacity: op }); } }, this); if (fw.length) (function() { this.runFirework(fw); }).delay(this.options.drawSpeed, this); }, /* startParallax: function() { this.parallax.bounds = { start: Math.round(this.width / 3 - 20) }; this.parallax.bounds.end = Math.round(this.parallax.bounds.start + 140); this.parallax.fx.setOptions({ duration: this.options.drawSpeed * this.width / this.lemmings[0].step }); console.log(this.options.drawSpeed * this.width / this.lemmings[0].step); console.log([((this.lemmings[0].direction == "r") ? this.parallax.bounds.start : this.parallax.bounds.end) + "px 0", ((this.lemmings[0].direction == "r") ? this.parallax.bounds.end : this.parallax.bounds.start) + "px 0"]); this.parallax.fx.start({ "background-position": [((this.lemmings[0].direction == "r") ? this.parallax.bounds.start : this.parallax.bounds.end) + "px 0", ((this.lemmings[0].direction == "r") ? this.parallax.bounds.end : this.parallax.bounds.start) + "px 0"] }); },*/ drawWorld: function() { this.lemmings.each(function(lem, i) { // is the lemming exploding? if (lem.exploding) { var spriteOffset = lem.el.getStyle("backgroundPosition") || 0, spriteOffsetX = spriteOffset.split(" ")[0].toInt(); var newOffset = (spriteOffsetX - this.options.spriteWidth <= this.options.spriteExplodeContainerWidth.neg()) ? 0 : spriteOffsetX - this.options.spriteWidth; if (newOffset == 0 && spriteOffsetX != 0) { var coords = lem.el.getPosition(); this.firework(coords.x + this.options.spriteWidth / 2, coords.y + this.options.spriteHeight / 2); this.lemmings[i].exploding = false; return; } lem.el.setStyles({ backgroundPosition: newOffset + "px 0px" }); return; } // walking animation... var spriteOffset = lem.el.getStyle("backgroundPosition") || 0, spriteOffsetX = spriteOffset.split(" ")[0].toInt(); var newOffset = (spriteOffsetX - this.options.spriteWidth <= this.options.spriteContainerWidth.neg()) ? 0 : spriteOffsetX - this.options.spriteWidth; if (lem.pos > this.width - this.options.spriteWidth) lem.pos = this.width - this.options.spriteWidth; // moving on-screen if (lem.direction == "r" && lem.step != 0) { this.parallax.bottomLayer++; if (lem.pos + this.options.spriteWidth < this.width) { var nl = lem.pos + lem.step; // actually move the sprite here lem.el.setStyles({ marginLeft: nl, backgroundPosition: newOffset + "px 0px" }); this.lemmings[i].pos = nl; } else { this.fireEvent("turn", "left"); this.lemmings[i].direction = "l"; this.setSprite(lem); // console.log("turning..."); } } else if (lem.step != 0) { // console.log(pos.left); var newOffset = (spriteOffsetX - this.options.spriteWidth <= this.options.spriteContainerWidth.neg()) ? 0 : spriteOffsetX - this.options.spriteWidth; this.parallax.bottomLayer--; if (lem.pos > 0) { var nl = lem.pos - lem.step; // actually move the sprite here lem.el.setStyles({ marginLeft: nl, backgroundPosition: newOffset + "px 0px" }); this.lemmings[i].pos = nl; } else { this.fireEvent("turn", "right"); this.lemmings[i].direction = "r"; this.setSprite(lem); } } // parallax effect on background this.container.setStyle("background-position", this.parallax.bottomLayer + "px 0"); // progress is relational to sun. if (nl != 0) var progress = (this.width / 2) - nl / 2; if (!isNaN(progress)) this.parallax.topLayer.setStyle("background-position", progress +"px 0"); var hours = new Date().get("hr"); // auto swap between themes if (this.options.daylightDetect) { if (hours > 8 && hours < 16 && this.theme != "D") { // 8am, daylight has come! this.theme = "D"; this.changeDaylight(); } if (hours < 8 && hours > 16 && this.theme == "D") { this.theme = ""; this.changeDaylight(); } }; }, this); }, changeDaylight: function() { if (Browser.Engine.trident) this.parallax.topLayer.setOpacity(0); this.container.fade(.3); (function() { this.setTheme(); }).delay(300, this); (function() { this.container.fade(1); }).delay(600, this); if (Browser.Engine.trident) (function() { this.parallax.topLayer.fade(1); }).delay(900, this); } }); Number.implement({ neg: function() { return this * -1; } });