Adaptive SVG with Hover
Utilizes resizeObserver in place of media queries to make changes based on element size, not viewport size.
by kthornbloom
HTML
<!-- ONLY FOR DEMO -->
<form>
<input type="range" id="sizer" min="16" max="128" value="60">
</form>
<div id="page">
<!-- Requires a wrapping div because resizeObserver doesn't work on SVG alone -->
<div class="icon--adaptive" title="yay, hover effects">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st1{display:inline;}
.st2{display:inline;fill:#1373A8;}
.ico-shadow{display:inline;fill:#1373A8; opacity:.2}
.st4{display:inline;fill:#0C436B;}
</style>
<!-- Groups within SVG for levels of fidelity -->
<g class="low-fidelity">
<path d="M28,12l-3-3h-4V6h-9v2h7v11h-8.8c-0.5-0.6-1.3-1-2.2-1s-1.7,0.4-2.2,1H4H2v2h3c0,1.7,1.3,3,3,3s3-1.3,3-3h10h1
c0,1.7,1.3,3,3,3s3-1.3,3-3h2v-8L28,12z M8,22c-0.6,0-1-0.4-1-1c0-0.6,0.4-1,1-1s1,0.4,1,1C9,21.6,8.6,22,8,22z M25,22
c-0.6,0-1-0.4-1-1c0-0.6,0.4-1,1-1s1,0.4,1,1C26,21.6,25.6,22,25,22z M28,19h-0.8c-0.5-0.6-1.3-1-2.2-1s-1.7,0.4-2.2,1H21v-8h3.2
l2.4,2.4l0.2,0.2l0.3,0.1l0.9,0.4V19z M17,12H9v-2h8V12z M7,12H5v-2h2V12z M6,14h8v2H6V14z M4,16H2v-2h2V16z"/>
</g>
<g class="medium-fidelity">
<path class="st1" d="M30,14l-3-2l-1-3h-4V6H8v1h13v2v10H9.5v0c-0.5-0.6-1.2-1-2-1s-1.5,0.4-2,1v0H4H3H2v1h3.1C5,20.2,5,20.3,5,20.5
C5,21.9,6.1,23,7.5,23s2.5-1.1,2.5-2.5c0-0.2,0-0.3-0.1-0.5H21h1h1.1c0,0.2-0.1,0.3-0.1,0.5c0,1.4,1.1,2.5,2.5,2.5s2.5-1.1,2.5-2.5
c0-0.2,0-0.3-0.1-0.5H30V14z M5.2,19.5C5.2,19.5,5.2,19.5,5.2,19.5C5.2,19.5,5.2,19.5,5.2,19.5z M7.5,22C6.7,22,6,21.3,6,20.5
S6.7,19,7.5,19S9,19.7,9,20.5S8.3,22,7.5,22z M9.8,19.5C9.8,19.5,9.8,19.5,9.8,19.5C9.8,19.5,9.8,19.5,9.8,19.5z M25.5,22
c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5S26.3,22,25.5,22z M29,19h-1.5v0c-0.5-0.6-1.2-1-2-1s-1.5,0.4-2,1v0H22v-9
...
SCSS
// Basic Icon Styling
.icon--adaptive {
display: inline-block;
height: 2em;
vertical-align: middle;
width: 2em;
transition: transform .25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transform:translateZ(0); // prevent artifacts
svg {
vertical-align: top;
}
.high-fidelity {
display: none;
}
.medium-fidelity {
display: none;
}
}
// Hides everything but medium fidelity
.icon--med-fidelity {
.low-fidelity {
display: none;
}
.medium-fidelity {
display: inline;
}
.high-low {
display: none;
}
}
// Hides everything but high fidelity
.icon--high-fidelity {
.low-fidelity {
display: none;
}
.medium-fidelity {
display: none;
}
.high-fidelity {
display: inline;
}
}
// Exmaple of a hover effect
.icon--adaptive:hover {
cursor: pointer;
transform: scale(1.1);
path {
fill: tomato;
}
}
// Demo stuff be here
body {
display: flex;
flex-align: center;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
background: radial-gradient(#fff, #ccc);
}
form {
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100%;
text-align: center;
padding: 10px;
}
JavaScript
// Set up variable for elements being targeted
const boxes = document.querySelectorAll('.icon--adaptive');
// Resize observer watches for size changes
const myObserver = new ResizeObserver(entries => {
// Loop the elements
for (let entry of entries) {
const width = Math.floor(entry.contentRect.width);
const svg = entry.target.firstElementChild;
const classList = svg.classList;
if (width >= 128) {
// Large, use Hi-Fi
classList.add('icon--high-fidelity')
classList.remove('icon--med-fidelity');
} else if (width >= 70 && width < 128) {
// Medium, use Med-Fi
classList.add('icon--med-fidelity');
classList.remove('icon--high-fidelity');
} else {
// Small use Low-Fi
classList.remove('icon--high-fidelity');
classList.remove('icon--med-fidelity');
}
}
});
// Watch each of your elements
boxes.forEach(box => {
myObserver.observe(box);
});
/* BELOW IS ONLY FOR DEMO */
function resizer(){
let scale = document.getElementById('sizer').value;
document.getElementById('page').style.fontSize = scale + "px";
}
var e = document.getElementById('sizer');
e.oninput = function() {
resizer();
};
resizer();