<div class="main-view-container">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
<svg id="PlayPauseButton" class="svg-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<g id="keylines" fill="none">
<circle id="keyline-outer-circle--play-button" cx="24" cy="24" r="22" stroke="#000" stroke-miterlimit="10" />
</g>
<g id="guide-shapes" fill="none">
<polygon id="guide-shape--pause-right" class="guide-element guide-element__pause-right" points="26.5,13 38,13 38,36 26.5,36" />
<polygon id="guide-shape--pause-left" class="guide-element__pause-left" points="10,13 21.5,13 21.5,36 10,36" />
</g>
<!--
In the start state, these will be shaped and positioned to
perfectly match the play triangle
-->
<g id="main-shapes">
<polygon id="pause-bar--left" class="shape-element shape-element__pause-left" points="18,13 24.35,16.585 24.35,32.415 18,36" />
<polygon id="pause-bar--right" class="shape-element shape-element__pause-right" points="24.2,16.5 38,24.5 38,24.5 24.2,32.5" />
</g>
</svg>
</svg>
</div>
body {
width: 100%;
height: 100vh;
position: relative;
background-color: lightgrey;
}
.main-view-container {
width: 80%;
height: 80%;
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
.svg-icon {
pointer-events: all;
}
View Compiled
const
DURATIONS = {
toggleIcon: 0.5
},
SELECTORS = {
pauseBarRight: {
origin: '.shape-element__pause-right',
destinationGuide: '.guide-element__pause-right'
},
pauseBarLeft: {
origin: '.shape-element__pause-left',
destinationGuide: '.guide-element__pause-left'
}
},
EASINGS = {
toggleIcon: Power2.easeOut // ~= linear-in-slow-out
},
boundUpdateAttr = function boundUpdateAttr(attr, value) {
this.setAttribute(attr, value.toString());
},
/**
* Helper that parses the "d" (for a path) or "points" (for a polygon) attribute
* from an SVG and returns a numeric array or, for paths, a mixed array of
* numeric values and string letters
*/
parsePathPointsAttr = function parsePathPointsAttr(attrString) {
// Split the string into path commands and points
let
pathExpression = /[achlmrqstvz]|(-?\d*\.?\d*(?:e[\-+]?\d+)?)[0-9]/ig,
path = attrString.match(pathExpression).map((n) => {
return isNaN(+n) ? n : +n;
});
// The first element needs to be a number, so remove if it's not
// Calling the string method will return the path with the removed element
path.prefix = isNaN(path[0]) ? path.shift() : '';
path.string = function() {
return path.prefix + path.join(' ');
};
return path;
},
/**
* onComplete and onReverseComplete callback that helps us
* track the proper state of the icon
*/
boundSetStateOnToggle = function boundSetStateOnToggle() {
this.isAnimating = false;
this.isShowingPlay = !this.isShowingPlay;
};
let init = ((svgElem, opts) => {
let
icon = Object.create(null),
wireUpPolygonPoints = function wireUpPolygonPoints() {
icon.POLYGON_POINTS = {
pauseBarRight: {
start: parsePathPointsAttr(icon.DOM_REFS.pauseBarRight.getAttribute('points')),
end: parsePathPointsAttr(icon.DOM_REFS.pauseBarRightGuide.getAttribute('points')),
correspondingDOMElem: icon.DOM_REFS.pauseBarRight
},
pauseBarLeft: {
start: parsePathPointsAttr(icon.DOM_REFS.pauseBarLeft.getAttribute('points')),
end: parsePathPointsAttr(icon.DOM_REFS.pauseBarLeftGuide.getAttribute('points')),
correspondingDOMElem: icon.DOM_REFS.pauseBarLeft
}
}
},
/**
* Creates a timeline that animates the play symbol the pause symbol,
* which is then ready to be reversed when we want to go in the opposite direction
*/
createPlayPauseTL = function createPlayPauseTL() {
let TL = new TimelineMax({
paused: true,
onComplete: boundSetStateOnToggle.bind(icon),
onReverseComplete: boundSetStateOnToggle.bind(icon)
});
// Animate polygons to their destinations by interpolating the
// "points" attribute for each
for (let pointSet of
[
icon.POLYGON_POINTS.pauseBarLeft,
icon.POLYGON_POINTS.pauseBarRight
]
) {
TL.to(
pointSet.start,
DURATIONS.toggleIcon, {
endArray: pointSet.end,
ease: EASINGS.toggleIcon,
// update the DOM with the value that's being interpolated
onUpdate: boundUpdateAttr.bind(
pointSet.correspondingDOMElem,
'points',
pointSet.start
)
},
0
);
}
return TL;
};
icon.svgElem = svgElem;
icon.isAnimating = false;
icon.isShowingPlay = true;
////// Cache DOM references and use then to compute our polygon start and end points
icon.DOM_REFS = {
pauseBarLeft: icon.svgElem.querySelector(SELECTORS.pauseBarLeft.origin),
pauseBarLeftGuide: icon.svgElem.querySelector(SELECTORS.pauseBarLeft.destinationGuide),
pauseBarRight: icon.svgElem.querySelector(SELECTORS.pauseBarRight.origin),
pauseBarRightGuide: icon.svgElem.querySelector(SELECTORS.pauseBarRight.destinationGuide)
};
wireUpPolygonPoints();
///// Once everything is computed in the DOM, make and cache our timeline
icon.mainIconTL = createPlayPauseTL();
icon.handleClick = function handleClick() {
if (!this.isAnimating) {
this.isAnimating = true;
if (this.isShowingPlay) {
// animate to the pause symbol
this.mainIconTL.play(0);
} else {
// Reverse back to the play symbol.
// NOTE: 0 sets the playhead at the end of the animation,
// and we reverse from there
this.mainIconTL.reverse(0);
}
this.isPlaying = !this.isPlaying;
}
};
icon.svgElem.addEventListener('click', icon.handleClick.bind(icon), false);
return icon;
});
let icon = init(document.querySelector('#PlayPauseButton'));
View Compiled
This Pen doesn't use any external CSS resources.