Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Today I wrote a JavaScript module (with module pattern) which generate gradient texts using canvas tags. This is my first JavaScript "program" (I don't know how to call it) created for a real need.

I would like to receive opinions about:

  • The performance factor: Is creating canvas slow? Or is it acceptable?
  • The maintainability factor: Will this code be handy for any changes?
  • The application of module pattern
  • The generale structure of code

Here is the module:

var CanvasGradientText = (function (doc, win) {
    var cg = {};
    var tags = [];
    var gradients = [];

    //Config
    cg.config = {
        dataPrefix: "cg"
    };

    //Wrapper which contains the tag and add some new features, a sort of decorator pattern
    function GradientCanvasTagWrapper(element) {
        this.element = element;
        this.gradientIDDataAttribute = "data-" + this.dataPrefix + "-gradientID";
        this.gradientTextDataAttribute = "data-" + this.dataPrefix + "-text";
        if (this.element.style.fontSize) this.setFontSize(element.style.fontSize);
        if (this.element.style.fontFamily) this.setFontFamily(element.style.fontFamily);
        if (this.element.style.textShadow) this.setTextShadow(shadowStringToObject(element.style.textShadow));
    }


    GradientCanvasTagWrapper.prototype.dataPrefix = cg.config.dataPrefix;
    GradientCanvasTagWrapper.prototype.fontSize = "16px";
    GradientCanvasTagWrapper.prototype.fontFamily = "Times New Roman";
    GradientCanvasTagWrapper.prototype.textShadow = {
        offsetX: 0,
        offsetY: 0,
        blur: 0,
        color: 0
    };
    GradientCanvasTagWrapper.prototype.setFontSize = function (fontSize) {
        this.fontSize = fontSize;
    };
    GradientCanvasTagWrapper.prototype.setFontFamily = function (fontFamily) {
        this.fontFamily = fontFamily;
    };
    GradientCanvasTagWrapper.prototype.setTextShadow = function (textShadow) {
        this.textShadow = textShadow;
    };
    GradientCanvasTagWrapper.prototype.getDataAttribute = function (data) {
        return this[data + "DataAttribute"] || false;
    };
    GradientCanvasTagWrapper.prototype.getFontSize = function () {
        return this.fontSize || false;
    };
    GradientCanvasTagWrapper.prototype.getTextShadow = function () {
        return this.textShadow || false;
    };
    GradientCanvasTagWrapper.prototype.getFontFamily = function () {
        return this.fontFamily || false;
    };

    //A class for single gradient
    function Gradient() {
        this.id = "";
        this.colorStops = [];
    }

    Gradient.prototype.direction = "vertical";
    Gradient.prototype.addColorStop = function (offset, color) {
        this.colorStops.push({
            offset: offset,
            color: color
        });
    };
    function isIE() {

        var ua = win.navigator.userAgent;
        var msie = ua.indexOf("MSIE ");
        return (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) || false;
    }

    // "white 10px 10px 10px" => {color:"white",offsetX:"10",offsetY:"10",blur:"10"}
    function shadowStringToObject(shadow) {
        var m = shadow.match(/(\d+(?=[a-zA-Z]{2})|rgb\(.+\)|(?!\s)[^px][a-zA-Z]+)/g);
        var res = {};
        if (!isIE()) {
            res.color = m[0];
            res.offsetX = m[1];
            res.offsetY = m[2];
            res.blur = m[3];
        } else {
            res.color = m[3];
            res.offsetX = m[0];
            res.offsetY = m[1];
            res.blur = m[2];
        }
        return res;
    }

    //Public method for register gradients
    cg.addGradient = function (gradient) {
        var id;
        var colorStops;
        if (gradient.id === undefined) {
            return false;
        } else {
            id = gradient.id;
        }
        if (gradient.colorStops.length < 1) {
            return false;
        } else {
            colorStops = gradient.colorStops;
        }
        var t = new Gradient;
        t.id = id;
        t.direction = gradient.direction || t.direction;
        for (var k in colorStops) {
            if (colorStops.hasOwnProperty(k)) {
                t.addColorStop(colorStops[k].offset, colorStops[k].color);
            }
        }
        gradients[id] = t;
        return true;
    };

    //Private method for getting gradients given the id
    function getGradient(id) {
        return gradients[id] || false;
    }

    //Private method for checking if a tag(contained in the wrapper) is valid
    function isLegalTag(tagWrapper) {
        var element = tagWrapper.element;
        if (element.offsetWidth < 1 || element.offsetHeight < 1) return false;
        var id = element.getAttribute(tagWrapper.getDataAttribute('gradientID'));
        var g = getGradient(id);
        return (id && g) || false;
    }

    //Public method for generating canvas element based on the tag wrapped by GradientCanvasTagWrapper
    //If the parameter is not wrapped, then wrap it, for allowing to use this method in the global scope
    cg.transform = function (tag) {
        var elementWrapper;
        var element;
        if (tag instanceof GradientCanvasTagWrapper) {
            elementWrapper = tag;
            element = elementWrapper.element;
        } else {
            element = tag;
            elementWrapper = new GradientCanvasTagWrapper(element);
        }
        var canvasTag = doc.createElement("canvas");
        var gradientID = element.getAttribute(elementWrapper.getDataAttribute('gradientID'));
        var gradient = getGradient(gradientID);
        var direction = gradient.direction;
        var fontSize = elementWrapper.getFontSize();
        var fontFamily = elementWrapper.getFontFamily();
        var textShadow = elementWrapper.getTextShadow();
        var centerX;
        var centerY;
        var ctx;
        var ctxGradient;
        canvasTag.id = "canvasTextGradient-" + element.id;
        canvasTag.width = element.offsetWidth;
        canvasTag.height = element.offsetHeight;
        centerX = canvasTag.width / 2;
        centerY = canvasTag.height / 2;
        ctx = canvasTag.getContext("2d");
        switch (direction) {
            case "vertical":
                ctxGradient = ctx.createLinearGradient(0, 0, 0, canvasTag.height);
                break;
            case "horizontal":
                ctxGradient = ctx.createLinearGradient(0, 0, canvasTag.width, 0);
                break;
            default:
                return false;
        }
        for (var k in gradient.colorStops) {
            if (gradient.colorStops.hasOwnProperty(k)) {
                ctxGradient.addColorStop(gradient.colorStops[k].offset, gradient.colorStops[k].color);
            }
        }
        ctx.fillStyle = ctxGradient;
        ctx.font = fontSize + " " + fontFamily;
        ctx.textAlign = "center";
        ctx.shadowColor = textShadow.color;
        ctx.shadowOffsetX = textShadow.offsetX;
        ctx.shadowOffsetY = textShadow.offsetY;
        ctx.shadowBlur = textShadow.blur;
        ctx.fillText(element.getAttribute(elementWrapper.getDataAttribute("gradientText")), centerX, centerY);
        element.appendChild(canvasTag);
        return true;
    };

    //Public method which should handle the configuration changes
    cg.setConfig = function (config) {
        cg.config.dataPrefix = config.dataPrefix || cg.config.dataPrefix;
    };

    //Public method for generate canvas in every tag which have determinate attributes
    cg.transformAll = function () {
        tags = doc.querySelectorAll("[data-" + cg.config.dataPrefix + "-active=true]");
        for (var k in tags) {
            if (tags.hasOwnProperty(k) && tags[k] instanceof HTMLElement) {
                var t = new GradientCanvasTagWrapper(tags[k]);
                if (!isLegalTag(t)) {
                    delete tags[k];
                } else {
                    this.transform(t);
                }
            }
        }
        return true;
    };
    return cg;
}(document, window));

CanvasGradientText.addGradient({id: "testGradient", direction: "vertical", colorStops: {0: {offset: "0", color: "#eaffb1"}, 1: {offset: "0.6", color: "#565e41"}, 2: {offset: "1.0", color: "#565e41"}}});

CanvasGradientText.addGradient({id: "testGradient2", direction: "horizontal", colorStops: {0: {offset: "0", color: "black"}, 1: {offset: "0.6", color: "black"}, 2: {offset: "1.0", color: "white"}}});

CanvasGradientText.transformAll();

Here is the implementation:

<div data-cg-gradientID="testGradient" data-cg-text="a random text" data-cg-active="true" style="
width:300px;
height:50px;
font-family:Georgia;
font-size:30px;
text-shadow: #495a1b 1px 2px 5px;">
</div>
<div data-cg-gradientID="testGradient2" data-cg-text="Another random text" data-cg-active="true" style="
width:300px;
height:50px;
font-family:Arial;
font-size:25px;
text-shadow: #495a1b 1px 2px 5px;">
</div>
<script type="text/javascript" src="js/canvasGradientText.js"></script>
<script type="text/javascript">
CanvasGradientText.addGradient({id:"testGradient",direction:"vertical",colorStops:{0:{offset:"0",color:"#eaffb1"},1:{offset:"0.6",color:"#565e41"},2:{offset:"1.0",color:"#565e41"}}});

CanvasGradientText.addGradient({id:"testGradient2",direction:"horizontal",colorStops:{0:{offset:"0",color:"black"},1:{offset:"0.6",color:"black"},2:{offset:"1.0",color:"white"}}});

CanvasGradientText.transformAll();
</script>

JSFiddle

These are my perplexities:

  • The switch block inside the trasnform method. I feel that it is wrong. I thought to create a oop interface for the direction, maybe using a decorator pattern, but I don't know how to do.
  • I also thought to make a sort of builder class which accept the context object as parameter in the constructor. At this point I would make methods like setDirection(DirectionObject), setGradient(GradientObject), setShadow(ShadowObject) etc. but I'm very confused about the real utility and implementation of this procedure.

Edit:
If I'll need to check if there is the data attribute, if so use it as text, otherwise use the text in the tag, there is a better way than putting this in the transform method:

var text = element.getAttribute(elementWrapper.getDataAttribute("gradientText");
if(text){
    ctx.fillText(text,centerX,centerY);
} else {
    ctx.fillText(element.innerHTML);
    element.innerHTML = "";
}
share|improve this question

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.