Morphing SVG Play Icon using D3.js
Morphing with d3 transition, selection and ease libraries. (Fake path values from YouTube player)
HTML
<script src="https://d3js.org/d3.v4.min.js"></script>
<button class="icon-button icon-button-play" aria-label="Play">
<svg class="morphing-icon morphing-icon-play" width="100%" height="100%" viewBox="0 0 36 36">
<defs>
<path id="icon-play" data-state="paused" data-next-state="playing" d="M 12,26 18.5,22 18.5,14 12,10 Z M 18.5,22 25,18 25,18 18.5,14 Z" />
<path id="icon-pause" data-state="playing" data-next-state="paused" d="M 12,26 16,26 16,10 12,10 Z M 21,26 25,26 25,10 21,10 Z" />
</defs>
<use xlink:href="#icon-play" />
</svg>
</button>
SCSS
.icon-button {
width: 144px;
height: 144px;
border: none;
background-color: transparent;
&:focus {
outline: none;
}
&:hover {
cursor: pointer;
}
}
JavaScript
class MorphingIcon {
constructor(icon) {https://jsfiddle.net/6bed532m/embedded/html,js,result/light/?username=laozhu#HTML
this.icon = document.querySelector(icon); // This Morphing Icon
this.states = this.initialStates(); // Candidate states object
this.using = this.states.using; // The using state
this.replaceWithUsingPath();
}
// Initial candidate states from svg defs tag, initial using state from use svg use tag.
initialStates() {
const states = {};
const paths = this.icon.querySelectorAll('defs path');
const usingPath = this.icon.querySelector('use').getAttribute('xlink:href');
const usingState = this.icon.querySelector(usingPath).getAttribute('data-state');
/* eslint-disable no-restricted-syntax */
for (const path of paths) {
const id = `#${path.getAttribute('id')}`;
const state = path.getAttribute('data-state');
const nextState = path.getAttribute('data-next-state');
const d = path.getAttribute('d');
states[state] = { id, state, nextState, d };
}
/* eslint-enable no-restricted-syntax */
states.using = usingState;
return states;
}
// Replace svg use tag with referred path tag, transition prepared.
replaceWithUsingPath() {
d3.select(this.icon.querySelector('use')).remove();
d3.select(this.icon).append('path')
.attr('class', 'using-path')
.attr('d', this.states[this.using].d);
}
// Switch to next state, transition duration, easingFunc and delay supported.
goToNextState(duration = 100, easingFunc = d3.easeLinear, delay = 0) {
if (this.states[this.using] !== undefined) {
this.states.using = this.states[this.using].nextState;
this.using = this.states.using;
if (this.states[this.using] !== undefined) {
d3.select(this.icon.querySelector('.using-path'))
.transition()
.duration(duration)
.ease(easingFunc)
.delay(delay)
.attr('d', this.states[this.using].d);
...