<!-- design inspired by https://dribbble.com/shots/4290719-Player -->
<!-- smooth parallax https://css-tricks.com/animated-intro-rxjs/ -->
<div class="toggles">
<button class="toggle--dark">
Dark Theme
</button>
<button class="toggle-light">
Light Theme
</button>
</div>
<div class="album">
<svg class="album__menu" height="12px" version="1.1" viewBox="0 0 18 12" width="18px"><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#000000" id="Core" transform="translate(-87.000000, -342.000000)"><g id="menu" transform="translate(87.000000, 342.000000)"><path d="M0,12 L18,12 L18,10 L0,10 L0,12 L0,12 Z M0,7 L18,7 L18,5 L0,5 L0,7 L0,7 Z M0,0 L0,2 L18,2 L18,0 L0,0 L0,0 Z" id="Shape"/></g></g></g></svg>
<div class="album__reflection"></div>
<div class="album__player">
<div class="album__song">
<h2 class="album__song__album">
<marquee scrollamount="3">
<!-- SOMEBODY STOP ME -->
Grouper - Grid of Points
</marquee>
</h2>
<h1 class="album__song__title">
The Races
</h1>
<h2 class="album__song__position">
track 01 of 07
</h2>
</div>
<div class="album__controls">
<svg class="album__prev" height="12px" viewBox="0 0 18 12" width="18px"><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#000000" id="Icons-AV" transform="translate(-43.000000, -5.000000)"><g id="fast-forward" transform="translate(43.000000, 5.000000)"><path d="M0,12 L8.5,6 L0,0 L0,12 L0,12 Z M9,0 L9,12 L17.5,6 L9,0 L9,0 Z" id="Shape"/></g></g></g></svg>
<svg class="album__play" height="20" viewBox="0 0 48 48" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M-838-2232H562v3600H-838z" fill="none"/><path d="M16 10v28l22-14z"/><path d="M0 0h48v48H0z" fill="none"/></svg>
<svg class="album__next" height="12px" viewBox="0 0 18 12" width="18px"><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#000000" id="Icons-AV" transform="translate(-43.000000, -5.000000)"><g id="fast-forward" transform="translate(43.000000, 5.000000)"><path d="M0,12 L8.5,6 L0,0 L0,12 L0,12 Z M9,0 L9,12 L17.5,6 L9,0 L9,0 Z" id="Shape"/></g></g></g></svg>
</div>
<div class="album__song__scrubber">
<input type="range" min="0" max="100" class="album__song__scrubber">
<span class="album__song__current-time">0:28</span>
<span class="album__song__full-length">0:50</span>
</div>
</div>
<!-- <img src="https://f4.bcbits.com/img/a3330461814_10.jpg" alt="" class="album__art"> -->
<div class="album__art"></div>
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/36124/profile/profile-80.jpg?1520103364" class="album__avatar">
<div class="album__interactions">
<svg enable-background="new 0 0 128 128" height="12px" id="Layer_1" version="1.1" viewBox="0 0 128 128" width="12px" class="album__like"><path d="M127,44.205c0-18.395-14.913-33.308-33.307-33.308c-12.979,0-24.199,7.441-29.692,18.276 c-5.497-10.835-16.714-18.274-29.694-18.274C15.912,10.898,1,25.81,1,44.205C1,79,56.879,117.104,64.001,117.104 C71.124,117.104,127,79.167,127,44.205z" fill="#232323"/></svg>
<svg enable-background="new 0 0 24 24" height="12px" id="Layer_1" version="1.1" viewBox="0 0 24 24" width="12px" class="album__add"><path clip-rule="evenodd" d="M22.5,14H14v8.5c0,0.276-0.224,0.5-0.5,0.5h-4C9.224,23,9,22.776,9,22.5V14H0.5 C0.224,14,0,13.776,0,13.5v-4C0,9.224,0.224,9,0.5,9H9V0.5C9,0.224,9.224,0,9.5,0h4C13.776,0,14,0.224,14,0.5V9h8.5 C22.776,9,23,9.224,23,9.5v4C23,13.776,22.776,14,22.5,14z" fill-rule="evenodd"/></svg>
</div>
<svg class="album__volume" enable-background="new 0 0 512 512" id="Layer_1" version="1.1" viewBox="0 0 512 512" xml:space="preserve" width="20" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M114.8,368.1H32.1c-5.8,0-10.5-4.7-10.5-10.5V154.4c0-5.8,4.7-10.5,10.5-10.5h82.7 c5.8,0,10.5,4.7,10.5,10.5v203.2C125.4,363.4,120.7,368.1,114.8,368.1z M42.7,347.1h61.6V165H42.7V347.1z" fill="#6A6E7C"/><path d="M303.7,512c-2.3,0-4.5-0.7-6.4-2.2L108.4,366c-2.6-2-4.2-5.1-4.2-8.4V154.4c0-3.3,1.5-6.4,4.2-8.4 L297.3,2.2c3.2-2.4,7.5-2.8,11.1-1.1c3.6,1.8,5.9,5.4,5.9,9.5v490.9c0,4-2.3,7.7-5.9,9.5C306.8,511.6,305.2,512,303.7,512z M125.4,352.4l167.7,127.8V31.8L125.4,159.6V352.4z" fill="#6A6E7C"/><path d="M393.6,334.9c-5.8,0-10.5-4.7-10.5-10.5V187.7c0-5.8,4.7-10.5,10.5-10.5c5.8,0,10.5,4.7,10.5,10.5v136.7 C404.1,330.2,399.4,334.9,393.6,334.9z" fill="#6A6E7C"/><path d="M479.9,392.4c-5.8,0-10.5-4.7-10.5-10.5V130.1c0-5.8,4.7-10.5,10.5-10.5c5.8,0,10.5,4.7,10.5,10.5v251.7 C490.4,387.7,485.7,392.4,479.9,392.4z" fill="#6A6E7C"/></g></svg>
</div>
@import url("https://fonts.googleapis.com/css?family=Arimo:400,400i,700,900");
// reset range
input[type="range"] {
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
width: 100%; /* Specific width is required for Firefox. */
background: transparent; /* Otherwise white in Chrome */
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
}
input[type="range"]:focus {
outline: none; /* Removes the blue border. You should probably do some kind of focus styling for accessibility reasons though. */
}
input[type="range"]::-ms-track {
width: 100%;
cursor: pointer;
/* Hides the slider so custom styles can be added */
background: transparent;
border-color: transparent;
color: transparent;
}
:root {
--width: 60vw;
--highlight: #fdfdfd;
--background: #1d1d1d;
--accent: #f70040;
--easing: cubic-bezier(0.645, 0.045, 0.355, 1);
--move-x: 0deg;
--move-y: 0deg;
@media screen and (max-width: 1090px) {
--width: 80vw;
}
@media screen and (max-width: 786px) {
--width: 90vw;
}
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
min-height: 100vh;
background: #f6f6f6; //ffecef
perspective: 90vw;
}
.toggles {
width: 100%;
text-align: center;
margin: -25px 0 25px;
}
.album {
// 685 x 390
position: relative;
width: var(--width);
height: 0;
box-shadow: 0px 50px 40px -35px rgba(0, 0, 0, 0.3);
background: var(--background);
display: grid;
grid-template-columns: 30% 60% 10%;
grid-template-rows: repeat(8, 1fr);
transform-style: preserve-3d;
transition: all 0.6s linear;
min-width: 685px;
max-width: 810px;
min-height: 390px;
max-height: 500px;
transform: rotateX(-45deg);
&.album--loaded {
transform: rotateX(0);
height: calc(var(--width) * 0.6);
}
&.album--parallax {
// transition: all 0.1s linear; // this kills older machines
transition: none;
transform: rotateX(var(--move-y)) rotateY(var(--move-x));
}
}
.album__menu {
width: 20px;
margin-top: 30px;
margin-left: 30px;
path {
fill: var(--highlight);
}
}
.album__reflection {
position: absolute;
width: 60%;
top: 0;
left: 30%;
bottom: 0;
box-shadow: 0px 30px 40px -15px rgba(0, 0, 0, 0.7);
}
.album__player {
position: relative;
display: contents;
grid-column: 1 / 1;
grid-row: 1 / -1;
z-index: 3;
}
.album__art {
position: relative;
grid-column: 2 / 2;
grid-row: 1 / -1;
display: block;
background: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/36124/grouper-grid-of-points.jpg")
50% 50% no-repeat;
background-size: cover !important;
max-width: 100%;
transform: scale(0);
transition: all 0.6s 0.3s var(--easing);
z-index: 2;
max-height: 100%;
.album--loaded & {
transform: scale(1);
}
}
.album__song {
grid-row: 2 / 3;
grid-column: 1 / 2;
margin-left: 40px;
font-family: "Arimo", sans-serif;
transform: translateZ(50px);
z-index: 3;
}
.album__song__album,
.album__song__position {
color: var(--highlight);
font-size: 12px;
margin: 10px 0;
transform: translateZ(35px) translateX(-100%);
opacity: 0;
transition: all 0.6s 0.6s var(--easing);
.album--loaded & {
opacity: 1;
transform: translateZ(35px) translateX(0);
}
}
.album__song__album {
margin-right: 20px;
margin-left: 5px;
}
.album__song__title {
font-size: calc(100vw * 0.07); // *shrug*
color: var(--accent);
line-height: 0.9;
font-weight: 900;
transform: translateX(-100%);
opacity: 0;
z-index: 3;
transition: all 0.3s 0.6s var(--easing);
.album--loaded & {
opacity: 1;
transform: translateX(0);
}
.album--parallax & {
transition: all 0.1s linear;
}
}
.album__controls {
grid-row: 6 / 7;
grid-column: 1 / 2;
padding-left: 40px;
display: flex;
align-items: center;
transform: translateZ(35px) translateX(-100%);
opacity: 0;
transition: all 0.8s 0.6s var(--easing);
.album--loaded & {
opacity: 1;
transform: translateZ(35px) translateX(0);
}
}
.album__song__scrubber {
grid-row: 7;
width: 70%;
margin: 10px auto 0;
transform: translateZ(35px) translateX(-100%);
opacity: 0;
transition: all 0.8s 0.6s var(--easing);
.album--loaded & {
opacity: 1;
transform: translateZ(35px) translateX(0);
}
}
.album__song__current-time,
.album__song__full-length {
color: var(--highlight);
font-size: 10px;
font-family: "Arimo", sans-serif;
display: block;
margin-top: 5px;
}
.album__song__current-time {
float: left;
}
.album__song__full-length {
float: right;
}
// range is a pain
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 5px;
width: 5px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
margin-top: -2px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */
}
input[type=range]::-moz-range-thumb {
-webkit-appearance: none;
height: 5px;
width: 5px;
border-radius: 50%;
border: none;
background: var(--accent);
cursor: pointer;
margin-top: -2px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */
}
input[type="range"]::-webkit-slider-runnable-track {
height: 1px;
background: var(--highlight);
width: 100%;
}
input[type=range]::-moz-range-track {
height: 1px;
background: var(--highlight);
width: 100%;
}
input[type="range"]:focus::-webkit-slider-runnable-track {
background: var(--highlight);
}
.album__prev,
.album__play {
margin-right: 13px;
}
.album__prev,
.album__next {
transform: translateZ(10px);
path {
fill: var(--highlight);
}
}
.album__prev {
transform-origin: 50%;
transform: rotateZ(180deg);
}
.album__play {
padding: 10px;
border: 2px solid var(--accent);
border-radius: 50%;
cursor: pointer;
transform: translateZ(10px);
path {
transform-origin: 50%;
}
path:nth-of-type(2) {
fill: var(--accent);
}
&:hover {
border: 2px solid var(--highlight);
path {
transform: scale(1.2);
}
}
}
.album__avatar {
width: 45px;
height: 45px;
grid-column: 3 / 4;
border-radius: 50%;
margin: 25px 0 0 15px;
transition: all 0.6s 0.3s var(--easing);
transform: translateY(25%) scale(0.3);
transform-origin: 50% 50%;
opacity: 0;
.album--loaded & {
// transition: filter 1s linear;
transform: translateY(0) scale(1);
opacity: 1;
}
@media screen and (max-width: 850px) {
width: 30px;
height: 30px;
margin-left: 25px;
}
}
.album__like {
margin: 15px 27px;
cursor: pointer;
padding: 5px;
border: 1px solid var(--accent);
border-radius: 50%;
transition: all 0.6s 0.6s var(--easing);
transform: translateY(25%) scale(0.3);
transform-origin: 50% 50%;
opacity: 0;
path {
fill: var(--accent);
}
&:hover {
border-color: var(--highlight);
path {
fill: var(--highlight);
}
}
.album--loaded & {
transform: scale(1) translateY(0);
opacity: 1;
}
.album--parallax & {
transition: all 0.1s linear;
}
}
.album__add {
margin: 0 27px;
cursor: pointer;
padding: 5px;
border: 1px solid #979797;
border-radius: 50%;
transition: all 0.6s 0.9s var(--easing);
transform: translateY(25%) scale(0.3);
transform-origin: 50% 50%;
opacity: 0;
path {
fill: var(--highlight);
}
&:hover {
border: 1px solid var(--accent);
path {
fill: var(--accent);
}
}
.album--loaded & {
transform: scale(1) translateY(0);
opacity: 1;
}
}
.album__volume {
grid-column: 3 / 4;
grid-row: 7 / -1;
transform-origin: 50%;
transform: rotateZ(-90deg);
margin: 15px 0 0 25px;
transition: all 0.6s 1.2s var(--easing);
transform: translateY(25%) scale(0.3);
transform-origin: 50% 50%;
opacity: 0;
path {
stroke: var(--highlight);
fill: var(--highlight);
stroke-width: 10px;
}
.album--loaded & {
transform: scale(1) translateY(0);
opacity: 1;
}
}
View Compiled
const body = document.querySelector("body");
const album = document.querySelector(".album");
let loaded = false;
const albumDetails = {
dark: {
album: "Grouper - Grid of Points",
song: "The Races",
art:
"https://s3-us-west-2.amazonaws.com/s.cdpn.io/36124/grouper-grid-of-points.jpg",
position: "01 of 07",
start: "0:27",
end: "0:50",
highlight: "#fdfdfd",
accent: "#f70040",
background: "#1d1d1d"
},
light: {
album: "Frontierer - Orange Mathematics",
song: "The Collapse",
art:
"https://s3-us-west-2.amazonaws.com/s.cdpn.io/36124/frontierer-orange-mathematics.jpg",
accent: "#f78900",
highlight: "#1d1d1d",
background: "#fdfdfd",
position: "03 of 16",
start: "1:00",
end: "2:08"
}
};
album.classList.add("album--loaded");
setTimeout(() => {
album.classList.add("album--parallax");
}, 1200);
function toggleThemes(theme) {
const album = albumDetails[theme];
const albumArt = document.querySelector(".album__art");
const albumTitle = document.querySelector(".album__song__album marquee");
const albumSong = document.querySelector(".album__song__title");
const albumPosition = document.querySelector(".album__song__position");
const albumCurrentTime = document.querySelector(".album__song__current-time");
const albumFullTime = document.querySelector(".album__song__full-length");
albumArt.style.background = `url(${album.art}) 50% 50% no-repeat`;
albumTitle.innerText = album.album;
albumSong.innerText = album.song;
albumPosition.innerText = album.position;
albumCurrentTime.innerText = album.start;
albumFullTime.innerText = album.end;
document.documentElement.style.setProperty("--highlight", album.highlight);
document.documentElement.style.setProperty("--background", album.background);
document.documentElement.style.setProperty("--accent", album.accent);
}
const buttons = document.querySelectorAll(".toggles button");
buttons.forEach(function(el) {
el.addEventListener("click", () => {
let theme = "";
if (el.innerText === "Dark Theme") {
theme = "dark";
} else {
theme = "light";
}
toggleThemes(theme);
});
});
// https://css-tricks.com/animated-intro-rxjs/
function smoothParallax() {
const body = document.querySelector("body");
const clientHeight = window.innerHeight;
const bodyDims = {
w: body.getBoundingClientRect().width,
h: body.getBoundingClientRect().height
};
const limit = {
x: 25,
y: 25
};
console.clear();
function lerp(start, end) {
const dx = end.x - start.x;
const dy = end.y - start.y;
return {
x: start.x + dx * 0.1,
y: start.y + dy * 0.1
};
}
const docEl = document.documentElement;
const mouseMove$ = Rx.Observable
.fromEvent(docEl, "mousemove")
.map(event => ({
x: event.clientX,
y: event.clientY
}));
const touchMove$ = Rx.Observable
.fromEvent(docEl, "touchmove")
.map(event => ({
x: event.touches[0].clientX,
y: event.touches[0].clientY
}));
const animationFrame$ = Rx.Observable.interval(10, Rx.Scheduler.animationFrame);
const move$ = Rx.Observable.merge(mouseMove$, touchMove$);
const smoothMove$ = animationFrame$
.withLatestFrom(move$, (frame, move) => move)
.scan(lerp);
console.log(smoothMove$);
smoothMove$.subscribe(pos => {
const clamped = {
x: pos.x / bodyDims.w * limit.x - limit.x / 2,
y: pos.y / bodyDims.h * limit.y - limit.y / 2
};
document.documentElement.style.setProperty("--move-y", `${clamped.y}deg`);
document.documentElement.style.setProperty("--move-x", `${clamped.x}deg`);
});
}
smoothParallax();
View Compiled
This Pen doesn't use any external CSS resources.