<!--
Ideally this would be a progressive enhancement, starting
with a checkbox and label and reacting to those events. I
wasn't really interested in that sort of exercise, hence
the lonely `<svg>` element!
The odd viewbox is compensating for my short-sightedness.
I didn't factor in space for shadows and animations in my
original Illustrator file, so I bumped the viewbox size
up to avoid having to adjust and re-export everything.
-->
<svg class="fave" width="140" height="140" viewBox="-20 -20 140 140">
<filter id="fave-shadow" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" />
<feOffset dx="0" dy="5" />
<feComponentTransfer>
<feFuncA type="linear" slope="0.25" />
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<circle class="fave-container" cx="50" cy="50" r="46" />
<g class="fave-star">
<path class="fave-star-main" d="M51.6,23.3l7.7,15.5l17.1,2.5c1.5,0.2,2.1,2,1,3.1L65,56.5l2.9,17.1c0.3,1.5-1.3,2.6-2.6,1.9
L50,67.4l-15.3,8.1c-1.3,0.7-2.9-0.4-2.6-1.9L35,56.5L22.6,44.4c-1.1-1-0.5-2.9,1-3.1l17.1-2.5l7.7-15.5C49,22,51,22,51.6,23.3z" />
<g class="fave-star-shading-light">
<path class="fave-star-shading-light-fill" d="M59.3,38.9l17.1,2.5c1.5,0.2,2.1,2,1,3.1L65,56.5l2.9,17.1c0.3,1.5-1.3,2.6-2.6,1.9L50,67.4L59.3,38.9z" />
<rect class="fave-star-shading-light-edge" x="53.1" y="38.1" transform="matrix(0.9511 0.309 -0.309 0.9511 19.089 -14.2817)" width="3" height="30" />
</g>
<path class="fave-star-shading-dark" d="M67.9,73.5L65,56.5l12.4-12.1c1.1-1,0.5-2.9-1-3.1L66.9,40L56.8,71l8.5,4.4C66.6,76.1,68.2,75,67.9,73.5z" />
<path class="fave-star-stroke" d="M50,26.9l6.6,13.3l0.7,1.4l1.6,0.2L73.6,44L62.9,54.3l-1.1,1.1L62,57l2.5,14.7l-13.2-6.9
L50,64l-1.4,0.7l-13.2,6.9L38,57l0.3-1.6l-1.1-1.1L26.4,44l14.7-2.1l1.6-0.2l0.7-1.4L50,26.9 M50,22.3c-0.6,0-1.3,0.3-1.6,1
l-7.7,15.5l-17.1,2.5c-1.5,0.2-2.1,2-1,3.1L35,56.5l-2.9,17.1c-0.2,1.2,0.7,2.1,1.8,2.1c0.3,0,0.6-0.1,0.8-0.2L50,67.4l15.3,8.1
c0.3,0.1,0.6,0.2,0.8,0.2c1,0,2-0.9,1.8-2.1L65,56.5l12.4-12.1c1.1-1,0.5-2.9-1-3.1l-17.1-2.5l-7.7-15.5
C51.3,22.7,50.6,22.3,50,22.3L50,22.3z" />
</g>
<g class="fave-spinner">
<path d="M54,30c0,1.1-0.9,2-2,2h-4c-1.1,0-2-0.9-2-2v-4c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2V30z" />
<path d="M38.7,33c0.8,0.8,0.8,2.1,0,2.8l-2.8,2.8c-0.8,0.8-2.1,0.8-2.8,0l-2.8-2.8c-0.8-0.8-0.8-2.1,0-2.8l2.8-2.8
c0.8-0.8,2.1-0.8,2.8,0L38.7,33z" />
<path d="M30,46c1.1,0,2,0.9,2,2v4c0,1.1-0.9,2-2,2h-4c-1.1,0-2-0.9-2-2v-4c0-1.1,0.9-2,2-2H30z" />
<path d="M33,61.3c0.8-0.8,2.1-0.8,2.8,0l2.8,2.8c0.8,0.8,0.8,2.1,0,2.8l-2.8,2.8c-0.8,0.8-2.1,0.8-2.8,0L30.2,67
c-0.8-0.8-0.8-2.1,0-2.8L33,61.3z" />
<path d="M46,70c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2v4c0,1.1-0.9,2-2,2h-4c-1.1,0-2-0.9-2-2V70z" />
<path d="M61.3,67c-0.8-0.8-0.8-2.1,0-2.8l2.8-2.8c0.8-0.8,2.1-0.8,2.8,0l2.8,2.8c0.8,0.8,0.8,2.1,0,2.8L67,69.8
c-0.8,0.8-2.1,0.8-2.8,0L61.3,67z" />
<path d="M70,54c-1.1,0-2-0.9-2-2v-4c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2v4c0,1.1-0.9,2-2,2H70z" />
<path d="M67,38.7c-0.8,0.8-2.1,0.8-2.8,0l-2.8-2.8c-0.8-0.8-0.8-2.1,0-2.8l2.8-2.8c0.8-0.8,2.1-0.8,2.8,0l2.8,2.8
c0.8,0.8,0.8,2.1,0,2.8L67,38.7z" />
</g>
</svg>
/* Colors */
$white: #FFFFFF;
$yellow: #F1D80A;
$yellow-dark: #EDCF08;
$teal: #12E79B;
$teal-light: #59FFB6;
$gold: #E1C422;
$gold-dark: #D9A62F;
$gold-darker: #CC8B36;
$gray-dark: #4D525C;
$purple: #26234e;
/* Defaults */
.fave {
cursor: pointer;
// https://css-tricks.com/snippets/css/remove-gray-highlight-when-tapping-links-in-mobile-safari/
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.fave-container {
fill: $teal;
stroke: $teal-light;
stroke-width: 6px;
filter: url(#fave-shadow);
}
.fave-star-main {
fill: $gray-dark;
}
.fave-star-shading-light,
.fave-star-shading-dark,
.fave-star-stroke {
visibility: hidden;
}
.fave-star-shading-light-fill {
fill: $gold-dark;
}
.fave-star-shading-light-edge,
.fave-star-shading-dark,
.fave-star-stroke {
fill: $gold-darker;
}
.fave-spinner {
visibility: hidden;
fill: $white;
}
/* States */
.fave.is-loading {
.fave-container {
fill: $gray-dark;
}
.fave-star {
visibility: hidden;
}
.fave-spinner {
visibility: visible;
}
}
.fave.is-checked {
.fave-container {
visibility: hidden;
}
.fave-star-main {
fill: $gold;
filter: url(#fave-shadow);
}
.fave-star-shading-light,
.fave-star-shading-dark,
.fave-star-stroke {
visibility: visible;
}
}
/* Demo styles */
html {
height: 100%;
}
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: $yellow;
background-image:
linear-gradient($purple 15%, rgba($purple, 0) 15%, rgba($purple, 0) 85%, $purple 85%),
radial-gradient($yellow-dark 25%, transparent 25%),
radial-gradient($yellow-dark 25%, transparent 25%);
background-size: 100% 100%, 10px 10px, 10px 10px;
background-position: 0 0, 0 0, 5px 5px;
}
/* Options */
var delay = 2; // fake loading duration in seconds
/* Elements */
var fave = document.querySelector('.fave');
var container = document.querySelector('.fave-container');
var spinner = document.querySelector('.fave-spinner');
var star = document.querySelector('.fave-star');
/* State */
var isPressed = false;
var isChanging = false;
var isChecked = false;
/* Press Down/Up */
var pressDown = function (event) {
if (isPressed) return;
isPressed = true;
document.addEventListener(
(event.type === 'touchstart') ? 'touchend' : 'mouseup',
pressUp);
TweenMax.to(fave, 0.5, {
scale: 0.8,
ease: Quad.easeOut
});
};
var pressUp = function (event) {
if (! isPressed) return;
document.removeEventListener(event.type, pressUp);
isPressed = false;
TweenMax.to(fave, 2, {
scale: 1,
ease: Elastic.easeOut.config(1.3, 0.2)
});
};
fave.addEventListener('touchstart', pressDown);
fave.addEventListener('mousedown', pressDown);
/* Favorite */
TweenMax.to(spinner, 1, {
rotation: 180,
transformOrigin: '50% 50%',
ease: Back.easeOut.config(1.8),
repeat: -1
});
var faveAnimation = new TimelineMax({
paused: true
});
faveAnimation.add(function () {
isChanging = true;
fave.classList.add('is-loading');
});
faveAnimation.to([container, spinner], 0.2, {
scale: 0,
transformOrigin: '50% 50%',
ease: Quad.easeIn
}, delay);
faveAnimation.add(function () {
fave.classList.remove('is-loading');
fave.classList.add('is-checked');
isChanging = false;
isChecked = true;
});
faveAnimation.from(star, 1, {
scale: 0,
transformOrigin: '50% 50%',
ease: Elastic.easeOut.config(1, 0.3),
immediateRender: false
});
fave.addEventListener('click', function () {
if (! isChanging) {
faveAnimation.play();
}
});
Also see: Tab Triggers