Skip to content

Instantly share code, notes, and snippets.



Created Jan 6, 2020
What would you like to do?
SVG Morphing Play Button
<div class="overflow">
<div id="play-button-container">
<svg id="button-circle-svg" viewBox="0 0 100 100">
<style type="text/css">
.st0 {
fill: #DDDDDD;
stroke-width: 0px;
<circle id="button-circle"
class="st0" cx="50" cy="50" r="50"/>
<svg id="play-triangle-svg" viewBox="0 0 100 100">
#play-triangle {
fill: black;
#triangle-tween-target {
display: none;
<polygon id="play-triangle" points="37,74.1 76.8,51.1 37,28.2 "/>
<rect id="triangle-tween-target" x="47.6" y="46.7" width="16" height="9"/>
<iframe id="youtube-video" width="560" height="315" src="" frameborder="0" gesture="media" allow="encrypted-media" allowfullscreen></iframe>
<div class="video-text">
Watch Video
var $triangleTweenTarget = $('#triangle-tween-target');
var $playTriangle = $('#play-triangle');
var clicked = false;
// In the next line, .progress(1).pause(0) is a tip from @greensock to force processing of morphs immediately
var morph ='#play-triangle', 0.5,
{ morphSVG: $triangleTweenTarget,
scale: 5,
transformOrigin: "center center"
var $pbContainer = $('#play-button-container');
var $buttonCircleSvg = $('#button-circle-svg');
var $buttonCircle = $('#button-circle');
var $playTriangleSvg = $('#play-triangle-svg');
$pbContainer.on('click', function(){
clicked =true;$buttonCircleSvg, 0.2, {scale: getScale(), opacity: 0.8});$buttonCircle, 0.5, {fill: '#333333'});;$playTriangleSvg, 0.5,
{ width: $(window).width(),
height: $(window).height(),
x: -1 * $pbContainer.offset().left,
y: -1 * $pbContainer.offset().top,
onStart: playVideo,
onComplete: moveVideo
} else {
clicked = false;$buttonCircleSvg, 0.5, {scale: 1});
morph.reverse();$buttonCircle, 0.5, {fill: '#DDDDDD'});$playTriangleSvg, 0.5,
{ width: $pbContainer.width(),
height: $pbContainer.height(),
x: 0,
y: 0,
scale: 1,
onStart: stopVideo
//get the minimum scale for the play button to cover the screen
function getScale() {
let buttonPosition =
{ x: $playTriangleSvg.offset().left + ($playTriangleSvg.width()/2),
y: $playTriangleSvg.offset().top + ($playTriangleSvg.height()/2)
let $window = $(window);
let farthestCorner =
{ x: (buttonPosition.x > $window.width() / 2) ? 0 : $window.width(),
y: (buttonPosition.y > $window.height() / 2) ? 0 : $window.height()
let a = Math.abs(buttonPosition.x - farthestCorner.x);
let b = Math.abs(buttonPosition.y - farthestCorner.y);
let hypotenuse = Math.sqrt(a*a + b*b);
return hypotenuse / ($playTriangleSvg.width() / 2);
function playVideo() {
var $youtubeVideo = $('#youtube-video');
var $playTriangle = $('#play-triangle');
function moveVideo() {
let pbcOffset = $pbContainer.offset();
let boundingBox = $playTriangle[0].getBoundingClientRect();
{ x: boundingBox.left - pbcOffset.left,
y: -,
width: boundingBox.width,
height: boundingBox.height});
function showVideo() {$youtubeVideo, 0.3, {opacity: 1});
function stopVideo() {
$youtubeVideo.removeClass('-visible');$youtubeVideo, 0, {opacity: 0});
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('youtube-video');
<script src=""></script>
<script src=""></script>
<script src=""></script>
<script src=""></script>
width: 100vw
height: 100vh
overflow: hidden
position: relative
top: 100px
left: 200px
width: 100px
height: 100px
cursor: pointer
position: absolute
top: 0
left: 0
position: absolute
top: 0
left: 0
opacity: 0
pointer-events: none
pointer-events: auto
padding-top: 110px
text-align: center
font-family: Arial

SVG Morphing Play Button

I wanted to create a smooth play button experience that naturally leads the user's eye from the area of focus (the play button) to the next place they'll want to be looking (the playing video). This allows the user's visual cortex (our brain's GPU) to handle the task transition which allows the rest of the cortex (our brain's CPU) to stay immersed in the website or content (h/t @rachelnabors).

I used two SVGs, a circle for the button which, after expanding, also serves as the overlay. The second SVG has two shapes, the play button triangle, and the 16:9 rectangle that it will morph into. Morphing was done using Greensock's MorphSVG plugin. Centering the video is done by setting the dimensions of the triangle SVG to match the window dimensions.

Post a comment if you have any questions!

Also thanks to @greensock for helping me solve a subtle bug with this that was preventing the video from centering.

A Pen by Eric Johnson on CodePen.



This comment has been minimized.

Copy link

@Yacine-22 Yacine-22 commented Jun 2, 2020

It doesn't work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.