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();