12

I have a lot of figures and elements drawn in the a HTML canvas. All of them have different colors, strokes, etc. I really don't like that all these values are wandering in my JS code as some styles are in the CSS and some are in the code.

Does somebody know a good way to define the styles in CSS and read the styles when actually rendering the objects?

Here is some example of what I need to do:

context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'green'; // I'd like to set with CSS
context.fill();
context.lineWidth = 5; // I'd like to set with CSS
context.strokeStyle = '#003300'; // I'd like to set with CSS
context.stroke();

http://jsfiddle.net/nedyalkov/ysgLqqh3/1/

Miroslav Nedyalkov
  • 1,101
  • 1
  • 10
  • 22
  • I do some canvas too and i think you cant use class cause canva dont really do DOM element however you can use save/restore if you have to use multiple time same style. [an example](http://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/) – Benjamin Poignant Apr 16 '15 at 12:54
  • While html canvas is a DOM element, it is really a container for the pixels that make up the shapes you draw onto the canvas. This means CSS cannot affect the shapes on the canvas. If you want to store your context shape styles separately from your app, you might look at storing those context shape styles in a separate JSON file which you can read at the start of your app. – markE Apr 16 '15 at 16:43

4 Answers4

12

I'm a bit late for the party but I just had the same scenario and I solved it like this:

// "Cache"
var classColors = {};

function getColor(className) {
    // Check for the color
    if (!classColors[className]) {

        // Create an element with the class name and add it to the dom
        $c = $('<div class="' + className + '"></div>').css('display', 'none');
        $(document.body).append($c);

        // Get color from dom and put it in the color cache
        classColors[className] = $c.css('color');

        // Remove the element from the dom again
        $c.remove();
    }

    // Return color
    return classColors[className];
}

I only needed the color but you can extend the cache object to hold more than color if you want. The you'd return a full object from the function. The below code is not tested at all:

var classProperties = {};

function getPropeties(className) {
    // Check for the properties object
    if (!classProperties[className]) {

        // Create an element with the class name and add it to the dom
        $c = $('<div class="' + className + '"></div>').css('display', 'none');
        $(document.body).append($c);

        // Get needed stuff from the dom and put it in the cache
        // P.S. Didn't test if canvas names are the same as css names.
        // If not you'll have to translate it
        classProperties[className] = {
            fillStyle: $c.css('color'),
            lineWidth: $c.css('border-width'),
            strokeStyle: $c.css('border-style')
        }

        // Remove the element from the dom again
        $c.remove();
    }

    // Return properties
    return classProperties[className];
}
Asken
  • 7,679
  • 10
  • 45
  • 77
5

Normally, people who draw a lot of stuff in a canvas will make their own shape objects with style properties. For example: http://jsfiddle.net/b93cc3ww/

context = document.getElementById("myCanvas").getContext("2d");

function Rectangle(params) {
    this.x = params.x || 0;
    this.y = params.y || 0;
    this.width = params.width || 0;
    this.height = params.height || 0;
    this.fillStyle = params.fillStyle || "#FFFFFF";
    this.strokeStyle = params.strokeStyle || "#000000";
    this.lineWidth = params.lineWidth || 0;
}

Rectangle.prototype.draw = function () {
    if (this.fillStyle) {
        context.fillStyle = this.fillStyle;
        context.fillRect(this.x, this.y, this.width, this.height)
    }
    if (this.strokeStyle && this.lineWidth) {
        context.strokeStyle = this.strokeStyle;
        context.lineWidth = this.lineWidth;
        context.strokeRect(this.x, this.y, this.width, this.height);
    }
}

rectangles = [
    new Rectangle({
        x: 10,
        y: 10,
        width: 300,
        height: 150,
        fillStyle: "#FF0000"
    }),
    new Rectangle({
        x: 250,
        y: 10,
        width: 100,
        height: 80,
        fillStyle: "#00FF00",
        strokeStyle: "#00AA00",
        lineWidth: 5
    }),
    new Rectangle({
        x: 10,
        y: 200,
        width: 250,
        height: 80,
        strokeStyle: "#FF0000",
        lineWidth: 1
    })
]

for (var i = 0; i < rectangles.length; ++i) {
    rectangles[i].draw();
}

If you like the way CSS works, you could make a shape object which can take a "class" parameter and then store a list of "clases" in an array at the top of your code.

Domino
  • 6,314
  • 1
  • 32
  • 58
  • 3
    This is just a roundabout way of saying to the OP: "no, you can't do it." OP wants to use actual CSS to render a canvas, and that's simply not possible. – Elad Stern Apr 16 '15 at 14:31
  • In my actual code I'm doing something like this - I have some object representing my high-level drawing objects and I have my style-related properties somewhere there, but I hope to find a way to separate the styling from the logic and more important - to keep all my colors, fonts and stuff in one place. I like you last suggestion, though - I just need to find a way to get all the actual values for a certain class - the DOM should have such an API. – Miroslav Nedyalkov Apr 16 '15 at 17:12
  • Just dropping in to add that if you're going to go with something like this, might as well make use of prototype inheritance. Make all objects of the same "class" inherit their style attributes from the same prototype instead of copying them down like in my example. – Domino May 07 '17 at 05:56
1

CSS has a certain scope to it, that is, it only acts on HTML elements. Javascript, on the other hand, has its own variables and can also interact with HTML elements. What you're trying to do is use CSS as variables for javascript, which can't be done.

The code sample above represents a piece of javascript which takes an HTML element (in this case a canvas) and performs a set of actions (methods) on it. What you're doing is literally drawing one line at a time to create your image, and this image is outside of the CSS scope as it is only defined by the elements internal properties, while CSS can only define its external (specifically, visual) properties.

Elad Stern
  • 1,072
  • 13
  • 23
  • "If a thirsty person is seeking water and looking to the right, but you know the water is on the left, it's kind to tell them to look to the left." In other words, CSS is not the best tool, but it's more kind to tell the questioner where they can find their solution (JS) rather than to answer by telling them "no water there". BTW, Firefox offers CSS variables: http://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables. We can hope that will be more widely adopted :-) – markE Apr 16 '15 at 16:45
  • Yes, I do understand that CSS will not somehow automatically be applied to my drawn figures. I just want to keep the styling of all the stuff separated from its logic (the JS code that renders the objects). @markE I need some solution that works in all browsers unfortunately :) – Miroslav Nedyalkov Apr 16 '15 at 17:08
  • @MiroslavNedyalkov, The styling solution typically used in this type of situation is to put the style definitions in a JSON file that is included with the app. That way you can customize styles per client if necessary. ;-) – markE Apr 16 '15 at 17:10
  • 1
    @markE this sounds reasonable, but this way I'll be able to customize only part of the properties per-client. Of course this is completely different story :) I'll put some more effort looking for solution and if I don't find a reasonable one I might stick to the JSON approach. – Miroslav Nedyalkov Apr 16 '15 at 17:14
  • @markE, the OP is looking to do something very specific which cannot be done. You could offer a different solution, I suppose, but since he was asking for something so specific it didn't seem appropriate. – Elad Stern Apr 19 '15 at 07:06
  • @EladStern. The Good Samaritan said: "No water to your right, but there's water over here to your left." – markE Apr 19 '15 at 16:13
1

A bit late to the game, but I think this is still useful so I created a jQuery free version of the accepted answer:

var classProperties = {};

function getPropeties(className)
{
  if (true === classProperties[className]) {
    return classProperties[className];
  }

  // Create an element with the class name and add it to the dom
  let element = document.createElement('div');
  element.style.display = 'none;'
  element.classList.add(className);
  document.body.appendChild(element);

  // Get needed stuff from the dom and put it in the cache
  let compStyles = window.getComputedStyle(element);

  classProperties[className] = {
    fill: compStyles.backgroundColor,
    lineWidth: compStyles.borderWidth,
    stroke: compStyles.borderColor
  }
  // Remove the element from the dom again
  element.remove();

  return classProperties[className];
}

Additionally, since I suffered through this: If you want to use this inside Vue you have to wait for the whole view to be mounted, otherwise there is a chance for the styles to not return the computed styles properly due to a race condition. See https://v2.vuejs.org/v2/api/#mounted for the necessary code.

I am certain this can be a problem for any vanilla JS code as well, when executed before the CSS is loaded.

tony19
  • 125,647
  • 18
  • 229
  • 307
Fjonan
  • 31
  • 4