Skip to content

Instantly share code, notes, and snippets.

@miyataken999
Created August 18, 2017 18:49
Show Gist options
  • Save miyataken999/153c1e60d242ea26722bb950d3153b4b to your computer and use it in GitHub Desktop.
Save miyataken999/153c1e60d242ea26722bb950d3153b4b to your computer and use it in GitHub Desktop.
Music Player UI
<div class="app">
<section class="player">
<img class="player__cover" :src="currentTrack.cover.large" alt="" />
<div class="player__timer">
<div class="player__timer__elapsed" v-text="player.elapsed | time"></div>
<div class="player__timer__total" v-text="currentTrack.duration | time"></div>
</div>
<div class="slider player__progress-bar">
<input type="range" :value="player.elapsed" :max="currentTrack.duration" />
</div>
<ul class="player__controls">
<li
class="control control--small"
v-bind:class="{
'control--active' : player.repeat,
'control--dimmed' : !player.repeat
}"
@click="toggleRepeat"
>
<svg class="icon" viewbox="0 0 100 100">
<use xlink:href="#repeat"></use>
</svg>
</li>
<li class="control" @click="skipBack">
<svg class="icon" viewbox="0 0 100 100" >
<use xlink:href="#skip-back"></use>
</svg>
</li>
<li class="control control--outlined">
<svg
class="icon"
viewbox="0 0 100 100"
@click="play"
v-if="!player.playing"
>
<use xlink:href="#play"></use>
</svg>
<svg
class="icon"
viewbox="0 0 100 100"
@click="pause"
v-if="player.playing"
>
<use xlink:href="#pause"></use>
</svg>
</li>
<li class="control" @click="skipForward">
<svg class="icon" viewbox="0 0 100 100">
<use xlink:href="#skip-forward"></use>
</svg>
</li>
<li
class="control control--small"
v-bind:class="{
'control--active' : player.shuffle,
'control--dimmed' : !player.shuffle
}"
@click="toggleShuffle"
>
<svg class="icon" viewbox="0 0 100 100">
<use xlink:href="#shuffle"></use>
</svg>
</li>
</ul>
<h1 class="player__title" v-text="currentTrack.title"></h1>
<h2 class="player__sub-title">{{currentTrack.album}} - {{currentTrack.artist}}</h2>
<div class="player__volume">
<div class="player__volume__icon">
<svg class="icon" viewbox="0 0 100 100">
<use xlink:href="#volume"></use>
</svg>
</div>
<div class="slider slider--volume player__volume__slider">
<input type="range" :value="player.volume" max="100" />
</div>
</div>
</section>
<aside class="playlist">
<header class="playlist__header">
<h1 class="playlist__title" v-text="playlist.title"></h1>
<div class="playlist__info">
<a href="#" v-text="playlist.author"></a> - {{playlist.tracks.length}} songs, {{playlistDuration | minutes}} min
<a href="#">
<svg class="icon icon--inline" viewbox="0 0 100 100">
<use xlink:href="#share"></use>
</svg>
</a>
</div>
</header>
<ol class="playlist__list">
<li
class="playlist__track"
@click="selectTrack($index)"
v-bind:class="{'playlist__track--active': player.currentTrack === $index}"
v-for="track in playlist.tracks"
>
<img :src="track.cover.small" class="playlist__track__cover" />
<div class="playlist__track__info">
<h3 class="playlist__track__title" v-text="track.title"></h3>
<span class="playlist__track__sub-title">{{track.album}} - {{track.artist}}</span>
</div>
<span class="playlist__track__time" v-text="track.duration | time"></span>
</li>
</ol>
</aside>
</div>
<svg xmlns="http://www.w3.org/2000/svg" class="hide">
<symbol id="play" viewBox="0 0 23.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23 11.6L6.7 2.4c-1.4-.8-2.6 0-2.6 1.8V22c0 1.8 1.2 2.7 2.6 1.8L23 14.6c1.5-.8 1.5-2.2 0-3z" />
</symbol>
<symbol id="repeat" viewBox="0 0 26.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.1 2.1v4h-10c-3.3 0-6 2.7-6 6 0 1 .3 1.9.7 2.8l2.5-1.8c-.1-.3-.2-.7-.2-1 0-1.7 1.3-3 3-3h10v4l8-5v-1l-8-5zm4.8 11c.1.3.2.7.2 1 0 1.7-1.3 3-3 3h-10v-4l-8 5v1l8 5v-4h10c3.3 0 6-2.7 6-6 0-1-.3-1.9-.7-2.8l-2.5 1.8z"
/>
</symbol>
<symbol id="share" viewBox="0 0 26.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.1 23.1h-20v-20h10l2-2h-12c-1.1 0-2 .9-2 2v20c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2v-7l-2 2v5zm-18-2c3-8 7-8 13-8v4l7-7-7-7v4c-13 0-13 10-13 14z" />
</symbol>
<symbol id="shuffle" viewBox="0 0 26.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.1 1.1v4h-3c-3.3 0-6 2.7-6 6v4c0 1.7-1.3 3-3 3h-4v3h4c3.3 0 6-2.7 6-6v-4c0-1.7 1.3-3 3-3h3v4l8-5v-1l-8-5zM6.4 8.4L8.2 6c-.9-.6-2-.9-3.1-.9h-4v3h4c.5 0 .9.1 1.3.3zm10.7 9.7h-3c-.5 0-.9-.1-1.3-.3L11 20.2c.9.6 2 .9 3.1.9h3v4l8-5v-1l-8-5v4z"
/>
</symbol>
<symbol id="skip-back" viewBox="0 0 26.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3 5.3l-8.2 4.8V6.7c0-1.3-.8-1.9-1.8-1.3L6.1 9.5V7.1c0-1.1-.9-2-2-2h-1c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h1c1.1 0 2-.9 2-2v-2.4l7.2 4.2c1 .6 1.8 0 1.8-1.3v-3.5l8.2 4.8c1 .6 1.8 0 1.8-1.3V6.7c0-1.4-.8-2-1.8-1.4z"
/>
</symbol>
<symbol id="skip-forward" viewBox="0 0 26.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.1 5.1h-1c-1.1 0-2 .9-2 2v2.4l-7.2-4.2c-1-.6-1.8 0-1.8 1.3v3.5L2.9 5.3c-1-.6-1.8 0-1.8 1.3v12.9c0 1.3.8 1.9 1.8 1.3l8.2-4.8v3.5c0 1.3.8 1.9 1.8 1.3l7.2-4.2V19c0 1.1.9 2 2 2h1c1.1 0 2-.9 2-2V7c0-1-.9-1.9-2-1.9z"
/>
</symbol>
<symbol id="volume" viewBox="0 0 26.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.1 13.1c0-3.3-2-6.2-4.9-7.4l-.8 1.8c2.2.9 3.7 3 3.7 5.5s-1.5 4.6-3.7 5.5l.8 1.8c2.9-1 4.9-3.9 4.9-7.2zm-4 0c0-1.7-1-3.1-2.5-3.7l-.8 1.8c.7.3 1.2 1 1.2 1.8s-.5 1.5-1.2 1.8l.8 1.8c1.5-.4 2.5-1.8 2.5-3.5zM17.7 2l-.8 1.8c3.6 1.5 6.2 5.1 6.2 9.2 0 4.2-2.5 7.7-6.2 9.2l.8 1.8c4.3-1.8 7.4-6.1 7.4-11.1s-3-9.1-7.4-10.9zM1.1 8.1v10h4l7 7v-24l-7 7h-4z"
/>
</symbol>
<symbol id="pause" viewBox="0 0 26.2 26.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.1 2.1h-4c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h4c1.1 0 2-.9 2-2v-18c0-1.1-.9-2-2-2zm14 0h-4c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h4c1.1 0 2-.9 2-2v-18c0-1.1-.9-2-2-2z" />
</symbol>
</svg>

Music Player UI

I stumbled across Ollie Barker's dribble shot and decided to try and code it all up. It was origionally an exercize to learn the new release of VueJS but I got a bit carried away.

Please note: The font and icons are from a different set as I couldn't get the right ones.

A Pen by Sam Beckham on CodePen.

License.

console.clear();
// Filters
// =======
Vue.filter('time', function(seconds) {
var minutes = Math.floor(seconds / 60),
seconds = Math.floor(seconds % 60);
if (seconds < 10) {
seconds = '0' + seconds;
}
return minutes + ':' + seconds;
});
Vue.filter('minutes', function(seconds) {
var minutes = Math.floor(seconds / 60);
return minutes;
})
// App
// =====
new Vue({
el: '.app',
data: function() {
return {
player: {
currentTrack: 0,
elapsed: 0,
playing: false,
repeat: false,
shuffle: true,
volume: 68
},
playlist: {
title: 'Designers MX - Open Space',
author: 'Kyle Barret',
tracks: [{
title: 'Window',
artist: 'The Album Leaf',
album: 'In a Safe Place',
duration: 274,
cover: {
small: 'http://is5.mzstatic.com/image/pf/us/r30/Music/y2004/m06/d11/h18/s05.tjsjltyd.100x100-75.jpg',
large: 'https://yuq.me/albums/79/690/3374011100.jpg'
}
}, {
title: 'Dayvan Cowboy',
artist: 'Boards of Canada',
album: 'The Campfire Headphase',
duration: 300,
cover: {
small: 'http://static.metacritic.com/images/products/music/8/a0c6c84a9d3ef6a577aee9a32d0b5b6f-98.jpg',
large: 'http://boardsofcanada.com/vinyl-reissues/_/img/WARPLP123R-Packshot-800.jpg'
}
}, {
title: 'Adrift',
artist: 'Tycho',
album: 'Awake',
duration: 283,
cover: {
small: 'http://cdn.ghostly.com/images/artists/34/albums/454/GI-208_1500x300_188_188.jpg',
large: 'http://cdn.ghostly.com/images/artists/34/albums/454/GI-208_1500x300_540_540.jpg'
}
}, {
title: 'Arrival at Sydney Harbour',
artist: 'Port Blue',
album: 'The Airship',
duration: 306,
cover: {
small: 'https://upload.wikimedia.org/wikipedia/ru/thumb/3/31/Port_Blue_-_The_Airship.jpg/200px-Port_Blue_-_The_Airship.jpg',
large: 'https://upload.wikimedia.org/wikipedia/en/8/8f/Theairship.jpg'
}
}, {
title: 'The Backwards Step',
artist: 'Hammock',
album: 'Chasing After Shadows',
duration: 289,
cover: {
small: 'https://upload.wikimedia.org/wikipedia/en/thumb/3/36/Chasing_after_shadows.jpg/100px-Chasing_after_shadows.jpg',
large: 'http://f1.bcbits.com/img/a3547927520_10.jpg'
}
}]
}
}
},
computed: {
currentTrack: function() {
return this.playlist.tracks[this.player.currentTrack];
},
playlistDuration: function() {
var duration = 0,
tracks = this.playlist.tracks,
i;
for (i = 0; i < tracks.length; i += 1) {
duration += tracks[i].duration;
}
return duration;
}
},
methods: {
pause: function() {
if (!this.player.playing) {
return;
}
this.$set('player.playing', false);
clearInterval(this.timer);
this.$set('timer', false);
},
play: function() {
if (this.player.playing) {
return;
}
var _this = this,
timer = setInterval(function() {
if (_this.player.elapsed >= _this.currentTrack.duration) {
_this.$set('player.elapsed', 0);
_this.skipForward();
}
_this.player.elapsed += .1;
}, 100);
this.$set('player.playing', true);
this.$set('timer', timer);
},
selectTrack: function(id) {
this.$set('player.currentTrack', id);
this.$set('player.elapsed', 0);
this.play();
},
skipForward: function() {
var track = this.player.currentTrack + 1;
track = track % this.playlist.tracks.length;
this.selectTrack(track);
},
skipBack: function() {
var track = this.player.currentTrack;
if (this.player.elapsed < 2) {
track = track - 1;
}
if (track < 0) {
track = 0;
}
this.selectTrack(track);
},
toggleRepeat: function() {
this.player.repeat = !this.player.repeat;
},
toggleShuffle: function() {
this.player.shuffle = !this.player.shuffle;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.0/vue.min.js"></script>
// Variables
// =========
// Colors
// ------
$color__white: #fff;
$color__black: #3f3d34;
$color__gray: #7f7c6b;
$color__gray--dark: #4a473c;
$color__gray--light: #f2f2f2;
$color__yellow: #f9b94e;
$color__orange: #f9774e;
$color__beige: #ddd8c8;
// Font sizes
// -------
$font-size: 14;
$font-size--alpha: 1.95;
$font-size--beta: 1.5;
@mixin font-size($size){
font-size: #{$size * $font-size}px;
font-size: #{$size}rem;
line-height: 1em;
margin: 0 0 #{$size * $font-size / 2}px;
margin: 0 0 #{$size / 2}rem;
}
// Widths
// ------
$width--main: 75em;
$width--player: 33em;
$width--playlist: $width--main - $width--player;
// Spacing
// -------
$spacing: 2rem;
$spacing--large: 3rem;
$spacing--small: 1rem;
// Base
// ====
*,
*:before,
*:after {
box-sizing: border-box;
}
html {
background-color: $color__orange;
background-image: linear-gradient(
135deg,
$color__yellow 0%,
$color__orange 100%
);
font-size: #{$font-size}px;
font-family: Nunito, arial, sans-serif;
font-weight: 400;
min-height: 100%;
}
.app {
color: $color__black;
box-shadow: 0 0 $spacing--small rgba($color__black, .6);
display: flex;
flex-wrap: wrap;
margin: 0 auto;
max-width: $width--main;
padding: 0;
width: 100%;
@media (min-width: $width--playlist) {
margin: $spacing--small auto;
}
@media (min-width: $width--main) {
margin: $spacing auto;
}
}
// Generic
// -------
a {
color: $color__orange;
text-decoration: none;
transition: color .4s;
&:hover,
&:focus {
color: lighten($color__orange, 10%);
}
}
img {
height: auto;
max-width: 100%;
}
// Components
// ==========
// Slider
// ------
$slider-height: 8px;
$slider__thumb-height: 20px;
$slider__thumb-width: 6px;
.slider {
line-height: 1em;
overflow: hidden;
padding: ($slider__thumb-height - $slider-height) / 2 0;
[type=range] {
appearance: none;
background: $color__gray--light;
height: $slider-height;
position: relative;
width: 100%;
&:focus {
outline: none;
}
&::-webkit-slider-thumb {
appearance: none;
background-color: $color__black;
border-radius: 99px;
cursor: pointer;
height: $slider__thumb-height;
position: relative;
transition: transform .2s;
width: $slider__thumb-width;
&:focus,
&:active {
transform: scale(1.3);
}
&:after {
background: $color__orange;
bottom: 0;
content: '';
display: block;
height: $slider-height;
margin-top: 0 - ($slider-height / 2);
pointer-events: none;
position: absolute;
right: $slider__thumb-width;
top: 50%;
width: 999px;
}
}
}
&--volume {
[type=range] {
&::-webkit-slider-thumb {
background-color: $color__white;
border: 3px solid $color__orange;
width: $slider__thumb-height;
&:after {
right: $slider__thumb-height - 6;
}
}
}
}
}
// Icon
// ----
.icon {
fill: currentcolor;
height: 100%;
width: 100%;
&--inline {
display: inline-block;
height: 1em;
width: 1em;
}
}
// Control
// -------
.control {
cursor: pointer;
margin: 0;
padding: $spacing--small;
transition: opacity .4s, color .4s;
&:hover,
&:focus {
opacity: .8;
}
&:active {
color: $color__orange;
transition: none;
}
&--small {
transform: scale(.4);
}
&--dimmed {
opacity: .6;
}
&--outlined {
border: 2px solid $color__gray--light;
border-radius: 100%;
}
&--active {
color: $color__orange;
}
}
// UI
// ====
// Player
// ------
.player {
background: $color__white;
text-align: center;
width: 100%;
@media (min-width: $width--player) {
width: percentage($width--player / $width--main);
}
&__title {
@include font-size($font-size--alpha);
}
&__sub-title {
@include font-size($font-size--beta);
color: $color__gray;
font-weight: 400;
}
&__cover {
display: block;
width: 100%;
}
&__timer {
background: $color__beige;
display: flex;
justify-content: space-between;
padding: $spacing--small;
}
&__progress-bar {
margin-top: 0 - $spacing--small;
}
&__controls {
display: flex;
list-style: none;
padding: $spacing--small $spacing;
}
&__volume {
display: flex;
padding: $spacing;
&__icon {
width: $spacing;
height: $spacing;
margin-right: $spacing--small;
}
&__slider {
width: 100%;
}
}
}
// Playlist
// --------
.playlist {
background: $color__gray--dark;
color: $color__gray--light;
width: 100%;
@media (min-width: $width--player) {
width: percentage($width--playlist / $width--main);
}
&__header {
background: $color__black;
padding: $spacing--large;
}
&__title {
@include font-size($font-size--alpha);
margin-top: 0;
}
&__info {
}
&__list {
list-style: none;
margin: 0;
padding: 0 $spacing--large;
}
&__track {
border-bottom: 1px solid $color__black;
cursor: pointer;
display: flex;
justify-content: space-between;
margin: 0;
padding: $spacing 0;
&--active {
color: $color__orange;
}
&__cover {
height: $spacing--large;
width: $spacing--large;
}
&__info {
margin: 0 $spacing;
width: 100%;
}
&__title {
@include font-size($font-size--beta);
}
}
}
// Overrides
// =========
.hide.hide {
display: none;
}
<link href="https://fonts.googleapis.com/css?family=Nunito:300,400" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment