3
\$\begingroup\$

I want to learn best practices for structuring these kinds of small jQuery plugin libraries. I believe I am using a bad design pattern and what I was going for is fewest line numbers and minimal code.

Please advise on any better practices and any ways I can reduce the code down further.

(function() {
    var animationSupport = false,
        animationString = 'animation',
        vendorPrefix = prefix = '',
        domPrefixes = ['Webkit', 'Moz', 'O', 'ms', 'Khtml'];

    $(window).load(function() {
        var body = document.body;
        if (body.style.animationName !== undefined) {
            animationSupport = true;
        }

        if (animationSupport === false) {
            for (var i = 0; i < domPrefixes.length; i++) {
                if (body.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
                    prefix = domPrefixes[i];
                    animationString = prefix + 'Animation';
                    vendorPrefix = '-' + prefix.toLowerCase() + '-';
                    animationSupport = true;
                    break;
                }
            }
        }
    });


    var $createKeyframeStyleTag = function(id) {
        return $("<style>").attr({
            class: "keyframe-style",
            id: id,
            type: "text/css"
        }).appendTo("head");
    };

    $.keyframe = {
        getVendorPrefix: function() {
            return vendorPrefix;
        },
        isSupported: function() {
            return animationSupport;
        },
        generate: function(frameData) {
            var frameName = frameData.name || "";
            var css = "@" + vendorPrefix + "keyframes " + frameName + " {";

            for (var key in frameData) {
                if (key !== "name") {
                    css += key + " {";

                    for (var property in frameData[key]) {
                        css += property + ":" + frameData[key][property] + ";";
                    }

                    css += "}";
                }
            }

            css = PrefixFree.prefixCSS(css + "}");

            var $frameStyle = $("style#" + frameData.name);

            if ($frameStyle.length > 0) {
                $frameStyle.html(css);

                var $elems = $("*").filter(function() {
                    this.style[animationString + "Name"] === frameName;
                });

                $elems.each(function() {
                    var $el, options;
                    $el = $(this);
                    options = $el.data("keyframeOptions");
                    $el.resetKeyframe(function() {
                        $el.playKeyframe(options);
                    });
                });
            } else {
                $createKeyframeStyleTag(frameName).append(css);
            }
        },
        define: function(frameData) {
            if (frameData.length) {
                for (var i = 0; i < frameData.length; i++) {
                    var frame = frameData[i];
                    this.generate(frame);
                }
            } else {
                this.generate(frameData);
            }
        }
    };

    var animationPlayState = "animation-play-state";
    var playStateRunning = "running";

    $.fn.resetKeyframe = function(callback) {
        var $el = $(this).css(vendorPrefix + animationPlayState, playStateRunning).css(vendorPrefix + "animation", "none");

        if (callback) {
            setTimeout(callback, 1);
        }
    };

    $.fn.pauseKeyframe = function() {
        $(this).css(vendorPrefix + animationPlayState, "paused");
    };

    $.fn.resumeKeyframe = function() {
        $(this).css(vendorPrefix + animationPlayState, playStateRunning);
    };

    $.fn.playKeyframe = function(frameOptions, callback) {

        var animObjToStr = function(obj) {
            obj = $.extend({
                duration: 0,
                timingFunction: "ease",
                delay: 0,
                iterationCount: 1,
                direction: "normal",
                fillMode: "forwards"
            }, obj);
            return [obj.name, obj.duration, obj.timingFunction, obj.delay, obj.iterationCount, obj.direction, obj.fillMode].join(" ");
        };

        var animationcss = "";

        if ($.isArray(frameOptions)) {
            var frameOptionsStrings = [];
            for (var i = 0; i < frameOptions.length; i++) {
                if (typeof frameOptions[i] === 'string') {
                    frameOptionsStrings.push(frameOptions[i]);
                } else {
                    frameOptionsStrings.push(animObjToStr(frameOptions[i]));
                }
            }
            animationcss = frameOptionsStrings.join(", ");
        } else if (typeof frameOptions === 'string') {
            animationcss = frameOptions;
        } else {
            animationcss = animObjToStr(frameOptions);
        }

        var animationkey = vendorPrefix + "animation";
        var pfx = ["webkit", "moz", "MS", "o", ""];

        var _prefixEvent = function(element, type, callback) {
            for (var i = 0; i < pfx.length; i++) {
                if (!pfx[i]) {
                    type = type.toLowerCase();
                }
                var evt = pfx[i] + type;
                element.off(evt).on(evt, callback);
            }
        };

        this.each(function() {
            var $el = $(this).addClass("boostKeyframe").css(vendorPrefix + animationPlayState, playStateRunning).css(animationkey, animationcss).data("keyframeOptions", frameOptions);

            if (callback) {
                _prefixEvent($el, 'AnimationIteration', callback);
                _prefixEvent($el, 'AnimationEnd', callback);
            }
        });
        return this;
    };

    $createKeyframeStyleTag("boost-keyframe").append(" .boostKeyframe{" + vendorPrefix + "transform:scale3d(1,1,1);}");

}).call(this);

Please see the full library here.

\$\endgroup\$
2
\$\begingroup\$

Interesting question, I would have pasted the whole file, it could have made for a more coherent review.

  • animationSupport is declared outside of $(window).load() even though it is only used in $(window).load(), I would declare it inside

    var animationSupport = ( body.style.animationName !== undefined );
    
  • The same goes for domPrefixes

  • Once you have a boolean assigned to animationSupport, you can replace if( animationSupport === false ) with if(!animationSupport)

  • It is mildly funny that you declare this var body = document.body; as your sugar syntax. Since you only access body.style I would have gone for var style = document.body.style;

  • I also noted that you call the array with the vendor prefixes domPrefixes

\$\endgroup\$
1
  • \$\begingroup\$ Thanks so much for your insight! I'm new here, I presumed the code review would be made on the link, I have now put the whole file in. \$\endgroup\$ – Jake Cattrall Nov 3 '14 at 12:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.