Search code examples
cssgoogle-chromesvg

context-stroke and context-fill for markers in SVGs on Chrome don't work, alternatives?


I am working on generating some SVGs for a report and have to draw lines of different colors with matching markers for start and end. In Firefox it appears they fixed this issue 14 days ago, but chrome doesn't understand the stroke="context-stroke" attribute on my markers in the def section of the SVG. I was wondering if there is an alternative method to solve this, or do I simply have to make and manage a marker for each color my lines can be. I am not even sure if I can change the styling using javascript, as the markers are defined in the defs sections and apply to all lines that use them.

I tried to define a marker with the attributes fill = context-stroke and stroke = context-stroke. These work on Firefox, but not chrome.


Solution

  • As you can tell from this answer by Erik Dahlström from 2013 the concept of context-stroke and context-fill values for inheriting colors has been around for quite some time in SVG2 drafts.

    Unfortunately, it's still not implemented by most major browsers/engines.

    As a workaround, you could clone markers with different fill or stroke colors using JavaScript.

    let strokeMarkerEls = document.querySelectorAll(".strokeMarker");
    let markerTemplate = document.querySelector("#marker");
    
    // init
    setMarkerColors(strokeMarkerEls, markerTemplate);
    
    function setMarkerColors(strokeMarkerEls, markerTemplate) {
      strokeMarkerEls.forEach((el) => {
    
        // get element's stroke color
        let style = window.getComputedStyle(el);
        let stroke = style.stroke;
    
        // convert stroke color to hex value – used as an ID suffix for markers
        let strokeHex = rgbToHex(stroke);
    
        // define marker ID based on color
        let markerId = `${markerTemplate.id}_${strokeHex}`;
        let svg = el.closest("svg");
        let defs = svg.querySelector("defs");
    
        // check if marker of this color already exists
        let newMarker = defs.querySelector("#" + markerId);
    
        // otherwise append it
        if (!newMarker) {
          let markerClone = markerTemplate.cloneNode(true);
          markerClone.id = markerId;
          let markerEl = markerClone.children[0];
          markerEl.setAttribute("fill", "#" + strokeHex);
          defs.appendChild(markerClone);
        }
        // apply marker style
        el.style.markerEnd = `url(#${markerId})`;
        //el.setAttribute('marker-end', `url(#${markerId})`)
      });
    }
    
    // convert rgb(a) to hex code
    function rgbToHex(color) {
      let colArray = color
        .replace(/[rgba(|rgb(|)]/g, "")
        .split(",")
        .map((val) => {
          return parseFloat(val);
        });
      let alpha = colArray[3] ? colArray[3] : "";
      if (alpha) {
        alpha = ((alpha * 255) | (1 << 8)).toString(16).slice(1);
      }
      let [r, g, b] = [colArray[0], colArray[1], colArray[2]];
      let hexColor =
        (r | (1 << 8)).toString(16).slice(1) +
        (g | (1 << 8)).toString(16).slice(1) +
        (b | (1 << 8)).toString(16).slice(1);
      return hexColor + alpha;
    }
    svg {
      width: 20em;
      overflow: visible;
    }
    <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <!-- reusable arrow path -->
        <path  id="markerPath" d="M 0 0 L 10 5 L 0 10 z" />
        <marker id="marker" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <use href="#markerPath" />
        </marker>
      </defs>
      <path class="strokeMarker" id="path0" d="M0 10 l 100 0" fill="none" stroke="rgba(0,0,0,0.5)" />
      <path class="strokeMarker" id="path1" d="M0 20 l 100 0" fill="none" stroke="red" />
      <path class="strokeMarker" id="path2" d="M0 30 l 100 0" fill="none" stroke="green" />
      <path class="strokeMarker" id="path3" d="M0 40 l 100 0" fill="none" stroke="orange" />
      <path class="strokeMarker" id="path3" d="M0 50 l 100 0" fill="none" stroke="blue" />
      <path class="strokeMarker" id="path4" d="M0 60 l 100 0" fill="none" stroke="blue" />
      <path class="strokeMarker" id="path5" d="M0 70 l 100 0" fill="none" stroke="orange" />
    </svg>

    This approach is based on the idea to create a marker template that can be cloned, changing IDs and fill attributes for the actual marker element:

      <defs>
        <!-- reusable arrow path -->
        <path  id="markerPath" d="M 0 0 L 10 5 L 0 10 z" />
        <marker id="marker" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <use href="#markerPath" />
        </marker>
      </defs>
    

    For testing: codepen example