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?
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>