Search code examples
csssvglessmaskbem

Layering masks in CSS


I'm trying to figure out if it is possible layer masks in CSS. Basically I have an SVG which denotes some kind of stage. This is displayed as a mask using mask-image and has a black background-color. Something along the lines of:

mask-image: url(image.svg);
background-color: #000;
...

Depending on some related state, I need to draw attention to that masked svg by layering another svg over it, for example and exclamation mark. This would have another colour, for example red, but could change depending on the circumstance.

A crude example is shown below where I have a button with some text. The square is an svg which only appears when some state (state A) exists, in which case a class is added to the div. The circle inside the square is the related state and only appears when square is state A is true, plus some additional state. So in a Less/BEM structure, this extra state would be implemented as a modifier as such:

&__state {
  mask-image: url(state.svg);
  background-color: #000;
  ...

  &--inner-state {
    mask-image: url(inner-state.svg);
    background-color: red;
    ...
  }
}

enter image description here

The button doesn't know anything about the svgs as I am dynamically adding divs and the above CSS depending on the state.

Is this easily doable in CSS? As mentioned, I'm using Less with a BEM structure but I don't necessarily need an answer related to those. I'm more interested in the concept.

Feel free to ask for more info if required and I would appreciate any help!


Solution

  • (Posting another answer because this is super different)

    Demo:

    (Ideally you should read explanation first but in case you want to see the final result first...)

    Frontend: https://codepen.io/devanshj/pen/OomRer

    Backend: https://glitch.com/edit/#!/mature-teller?path=server.js:8:0

    Explanation:

    I was unable understand what are you trying to achieve with those masks and backgrounds (instead of direct svgs) till I came across this article: Coloring SVGs in CSS Background Images...

    The reason solution(s) in this article doesn't work for you is because you want multiple colored svgs (and the solution in the article is for single svg only)

    Combining the problem that the article tackles, with what you want as a final goal, I would reframe the question something like this:

    1. I want to have multiple svg backgrounds on an element
    2. I also want to control the color of the each svg
    3. Only css solution. You are not allowed to change html.

    This is pretty impossible. "pretty" I said, hence it's possible (with super extra-ordinary solution).

    The pretty impossible part is coloring the svg. Multiple svg backgrounds with only css is simple.

    But what if I do something that colors the svg from the url itself, like url(my-icon.svg?fill=#f00) If I can do this your problem is solved and we can do something like:

    .icon-a{
        background-image: url("a.svg?fill=%23000"); // # encoded to %23 so that server doesn't think #000 is the hash part of the request
    
        &.icon-b{
            background-image: url("a.svg?fill=%23000"), url("b.svg?fill=%23fff");
    
            &:hover{
                background-image: url("a.svg?fill=%23000"), url("b.svg?fill=%23f00");
            }
        }
    }
    

    This can be done with a little server code (using express and nodejs here, similar code can be written for other languages/frameworks too):

    app.get("/icons/*.svg", async (req, res) => {
        let svgCode = await readFileAsync(path.join(__dirname, req.path), {encoding: "utf8"});
        svgCode = svgCode.replace(/{{queryFill}}/g, req.query.fill);
        res.set("Content-Type", "image/svg+xml");
        res.send(svgCode);
    });
    

    and svg:

    <svg xmlns="http://www.w3.org/2000/svg">
        <style>
            .query-fill{
                fill: {{queryFill}};
            }
        </style>
        <rect x="0" y="0" width="100" height="100" class="query-fill"></rect>
    </svg>
    

    Alternative:

    If you allow me to change the html, the solution will be super easy by using inline svg and <use>... But I guess you want css-only solution...

    PS: In case you are wondering is there any solution without any server code? No.

    PPS : If the reframed question is not what you want, then the thing that you are looking for (layered masks) is not possible with only css.