skip to Main Content

Shifting focus to a group element in an svg

I have a tall and wide svg, generated by graphviz.

If I open the svg with a browser I can search for
text and focus will move to show the sought text.

I wish to emulate this behaviour by putting a link
to a svg fragment in an html file. So if I have file.svg
containing <g id="sought"><text>goes here</text></g>
the link would look like <a href="file.svg#sought">.

This doesn’t have the desired effect.

Experimentation reveals that a link to a svg file
containing <rect id="sought" /> will make
the browser move focus to the rect concerned.

I appreciate I may be asking for the impossible here,
but if there’s an answer that doesn’t involve changing the
svg I’d like to hear about it.

Minimal example follows:

pic.svg

<?xml version="1.0" 
      encoding="UTF-8" 
      standalone="no"?>
<svg 
    xmlns:svg="http://www.w3.org/2000/svg" 
    xmlns="http://www.w3.org/2000/svg"
    version="1.1" 
    width="6000" height="6000" y="0" x="0">
    <rect id="black" fill="black" height="100" width="100" y="50" x="5500" />
    <text y="50" x="5500">black</text>
    <rect id="red" fill="red" height="100" width="100" y="5500" x="5500" />
    <text y="5500" x="5500">red</text>
    <g id="green">
        <rect fill="green" height="100" width="100" y="5500" x="50" />
    <text y="5500" x="50">green</text>
    </g>
</svg>

svglink.html

<!DOCTYPE html>
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
body
{
    width: 20em;
    height: 10em;
    text-align: left;
}
</style>
</head>
<body >
<span>links to pic.svg: </span>
<ul>
<li><a href="pic.svg#red">red rect</a></li>
<li><a href="pic.svg#green">green group</a></li>
</ul>
</body>
</html>

Please note:

  • following the red rect link reveals the red rectangle in pic.svg.

  • following the green group link doesn’t reveal the green rectangle contained in the group

  • ctrl-f red|green|black when browsing pic.svg shifts focus to to the text, as desired.

This may well be by design, but I haven’t found any documentation
for this.

Modifying the html, perhaps scripting a button to reveal the group would be an option?

2

Answers


  1. Embed the svg in a page and it works
    So have a link to embeddedsvg.html#red

    <ul>
      <li><a href="#red">red rect</a></li>
      <li><a href="#green">green group</a></li>
    </ul>
    
    <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="6000" height="6000" y="0" x="0">
      <rect id="black" fill="black" height="100" width="100" y="50" x="5500" />
      <text y="50" x="5500">black</text>
      <rect id="red" fill="red" height="100" width="100" y="5500" x="5500" />
      <text y="5500" x="5500">red</text>
      <g id="green">
        <rect fill="green" height="100" width="100" y="5500" x="50" />
        <text y="5500" x="50">green</text>
      </g>
    </svg>
    Login or Signup to reply.
  2. Apparently some browser can only scroll/jump to SVG rendered elements with geometry properties. While you can retrieve position or bounding box data for a <g> (e.g via getBBox() element it doesn’t have intrinsic dimensions.

    scrollIntoView()

    A workaround might be to override the default scroll in view behaviour and replace it via JavaScript scrollIntoView() method.
    target.scrollIntoView() suffers from the same limitations. However, we can tweak the target selection:

    If a targeted element is not of the type path, line, rect, polyline, polygon, circle, ellipse, use or text – so for instance a <g> – we query for the next child element in this class to define it as the scroll target.

    //reset default anchor scroll
    let hash = window.location.hash;
    let targetId = hash ? hash.substring(1) : '';
    scrollToSVGEl(targetId);
    
    
    function scrollToSVGEl(targetId) {
      let target = targetId ? document.getElementById(targetId) : '';
      let renderedEls = ['path', 'line', 'rect', 'polyline', 'polygon', 'circle', 'ellipse', 'text', 'use'];
      if (!target) return false;
    
      let type = target.nodeName
    
      // select rendered element if target is group
      if (!renderedEls.includes(type)) {
        target = target.querySelector(`${renderedEls.join(', ')}`)
      }
      target.scrollIntoView({
        behavior: "smooth"
      });
    }
    
    
    // override default link behaviour
    let links = document.querySelectorAll('.aSvg')
    links.forEach(lnk => {
      lnk.addEventListener('click', e => {
        e.preventDefault();
        targetId = e.currentTarget.href.split('#').slice(-1)
        window.location.hash = targetId;
        scrollToSVGEl(targetId)
      })
    })
    html,
    body {
      scroll-behavior: smooth;
    }
    
    html,
    body,
     :target,
     :target * {
      scroll-padding: 100px;
    }
    
    svg {
      outline: 1px solid #ccc;
    }
    
    nav {
      background: rgba(0, 0, 0, 0.3);
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      a {
        margin-right: 1em;
      }
    }
    
    text {
      font-size: 24px;
    }
    <nav>
    
      <p><a class="aSvg" href="#black">black rect</a>
        <a class="aSvg" href="#red">red rect </a>
        <a class="aSvg" href="#green">green </a>
        <a class="aSvg" href="#text">text group</a>
        <a class="aSvg" href="#text2">text group 2</a>
      </p>
    
    
    </nav>
    
    
    <svg width="4000" viewBox="0 0 4000 5000">
            <rect id="black" x="30px" y="20px" width="250px" height="70px" fill="black" />
            <rect id="red" x="250px" y="400px" width="260px" height="100px" fill="red" />
            <g id="green">
                <rect x="500px" y="1200px" width="50px" height="50px" fill="green" />
                <g id="text">
                    <text x="3500" y="3200">Text</text>
    
                    <g id="text2">
                        <text x="500" y="3600">Text 2</text>
                    </g>
    
                </g>
            </g>
        </svg>

    This approach requires your SVG to be embedded in a HTML document. However, you could probably also add the script to your SVG (probably not very feasible).

    scrollTo()

    An alternative would be to retrieve the target’s screen coordinates via getBoundingClientRect() and scroll to this position via scrollTo()

    //reset default anchor scroll
    let hash = window.location.hash;
    let targetId = hash ? hash.substring(1) : '';
    scrollToSVGEl(targetId);
    
    
    function scrollToSVGEl(targetId) {
      let target = targetId ? document.getElementById(targetId) : '';
      if (!target) return false;
      let {
        top,
        left
      } = target.getBoundingClientRect()
      let x = left + window.scrollX;
      let y = top + window.scrollY;
      window.scrollTo({
        top: y,
        left: x,
        behavior: 'smooth'
      });
    }
    
    
    // override default link behaviour
    let links = document.querySelectorAll('.aSvg')
    links.forEach(lnk => {
      lnk.addEventListener('click', e => {
        e.preventDefault();
        targetId = e.currentTarget.href.split('#').slice(-1)
        window.location.hash = targetId;
        scrollToSVGEl(targetId)
      })
    })
    html,
    body {
      scroll-behavior: smooth;
    }
    
    html,
    body,
     :target,
     :target * {
      scroll-padding: 100px;
    }
    
    svg {
      outline: 1px solid #ccc;
    }
    
    nav {
      background: rgba(0, 0, 0, 0.3);
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      a {
        margin-right: 1em;
      }
    }
    
    text {
      font-size: 24px;
    }
    <nav>
      <p><a class="aSvg" href="#black">black rect</a>
        <a class="aSvg" href="#red">red rect </a>
        <a class="aSvg" href="#green">green </a>
        <a class="aSvg" href="#text">text group</a>
        <a class="aSvg" href="#text2">text group 2</a>
      </p>
    </nav>
    
    
    <svg width="4000" viewBox="0 0 4000 5000">
            <rect id="black" x="30px" y="20px" width="250px" height="70px" fill="black" />
            <rect id="red" x="250px" y="400px" width="260px" height="100px" fill="red" />
            <g id="green">
                <rect x="500px" y="1200px" width="50px" height="50px" fill="green" />
                <g id="text">
                    <text x="3500" y="3200">Text</text>
                </g>
    
                <g id="text2">
                    <text x="500" y="3600">Text 2</text>
                </g>
            </g>
    </svg>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search