<head>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Josefin+Sans:300,400,700&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/20d5c757b7.js" crossorigin="anonymous"></script>
</head>
<body>
<div id="root"></div>
</body>
$light: #FDFDFD;
$dark: #444444;
$shadow: #c4c4c4;
$accent: #583D91;
$anim: 0.15s;
html, body{
font-family: 'Josefin Sans', sans-serif;
font-size: 10px;
}
body, #root{
display: flex;
align-items: center;
justify-content: center;
background-color: #bbddee;
width: 100%;
height: 100%;
margin: 0;
}
body{
min-height: 100vh;
}
i{
text-align: center;
transition: $anim;
&.fa-forward{
padding-left: 4px;
}
&.fa-backward{
padding-right: 4px;
}
&.fa-play{
padding-left: 3px;
}
}
.player{
position: relative;
display: flex;
width: 32rem;
height: 64rem;
font-size: 1.6rem;
overflow: hidden;
}
.btn{
&--play{
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 4rem;
height: 4rem;
margin-left: 1rem;
border-radius: 1.2rem;
background-color: rgba($dark, 0.2);
border: none;
transition: $anim;
i{
opacity: 0.7;
}
&:not(:disabled):hover{
border-radius: 50%;
i{
opacity: 1;
}
}
&:focus{
outline: none;
box-shadow: 0 0 0.5rem $shadow;
}
}
&--list{
}
}
.list{
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 0 0 1rem;
background-color: $light;
border-radius: 1rem;
box-sizing: border-box;
overflow: hidden;
&__wrapper{
padding: 2rem 0 6rem;
flex: 1;
width: calc(100% + 1.7rem);
overflow-y: auto;
&.bg{
.list__item{
transform: translate3d(0, 2rem, -2rem) scale(0.9);
opacity: 0.5;
}
.list__cover{
transform: translateX(-2rem);
opacity: 0;
}
.info__duration{
opacity: 0;
}
}
}
&__item{
position: relative;
width: 100%;
display: flex;
align-items: center;
padding: 0.6rem 3rem 0.6rem 1.5rem;
transition: $anim*2 ease-in;
box-sizing: border-box;
background-color: $light;
&:before{
content: '';
position: absolute;
top: 0;
left: 0;
z-index: -1;
width: 100%;
height: 100%;
background-image: linear-gradient(to right, rgba($accent, 0.2), rgba($accent, 0.02));
opacity: 0;
transition: $anim*2 ease-out;
transform: translateX(-100%);
}
&:not(:disabled):hover{
transition-delay: 0s !important;
}
&:not(:disabled):hover, &.active{
z-index: 2;
&:before{
transform: translateX(0);
opacity: 1;
}
}
}
&__cover{
position: relative;
z-index: 2;
display: flex;
width: 4rem;
height: 4rem;
border-radius: 1.2rem;
overflow: hidden;
margin: 0;
filter: drop-shadow(0 0.3rem 0.3rem rgba($shadow, 0.2));
transition: $anim*2;
img{
position: absolute;
width: 100%;
height: 100%;
}
}
&__title{
position: relative;
display: flex;
align-items: center;
height: 8rem;
font-size: 2.4rem;
padding: 0 1.5rem 0 3rem;
color: $light;
background-color: $accent;
margin: 0;
border-radius: 0 0 4rem 0;
}
&__action{
position: absolute;
z-index: 10;
bottom: -2.5rem;
right: 2rem;
width: 5.4rem;
height: 5.4rem;
border-radius: 50%;
background-color: $light;
box-shadow: 0 0.5rem 1rem rgba($accent, 0.3);
transition: $anim;
opacity: 0;
transform: scale(0);
animation: scale $anim*2 forwards;
i{
color: $accent;
font-size: 2rem;
}
&:hover{
box-shadow: 0 0.3rem 0.6rem rgba($accent, 0.4);
}
}
}
.info{
position: relative;
display: flex;
flex-direction: column;
justify-content: space-around;
flex: 1;
width: calc(100%);
height: 100%;
cursor: pointer;
padding: 1rem;
box-sizing: border-box;
border-radius: 1.2rem;
transition: $anim;
&__author, &__name{
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
&__author, &__duration{
color: rgba($dark, 0.6);
font-size: 1.2rem;
}
&__name{
font-size: 1.4rem;
font-weight: 700;
margin: 0 auto 0.5rem 0;
}
&__status{
position: absolute;
bottom: -0.5rem;
left: 1.5rem;
display: flex;
width: calc(100% - 3rem);
height: 0.2rem;
border-radius: 0.1rem;
background-color: rgba($light, 0.5);
span{
height: 100%;
background-color: rgba($light, 0.8);
}
}
&__duration{
transition: $anim*2;
}
}
.song{
position: absolute;
top: 100%;
left: 0;
z-index: 2;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
background-color: $accent;
border-radius: 2rem;
padding-bottom: 8rem;
box-sizing: border-box;
transform: translateY(-58rem);
transition: $anim*3;
box-shadow: 0 0rem 1rem rgba(#2B0063, 0.4);
animation: popup $anim*3 forwards;
// animation: fullpopup $anim*2 forwards;
&__cover-wrapper, &__info{
width: 100%;
height: 50%;
flex: 1;
}
&__cover-wrapper{
position: relative;
display: flex;
align-items: flex-end;
transition: $anim*2;
}
&__cover{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
align-items: flex-end;
border-radius: 1rem 1rem 50% 50%;
box-shadow: 0 0.5rem 0.5rem rgba($dark, 0.3);
transition: $anim*2;
transition-delay: $anim*2;
img{
position: absolute;
top: 50%;
left: 50%;
z-index: -1;
min-width: 100%;
width: 100%;
min-height: 100%;
transform: translate(-50%, -50%);
}
}
&__info{
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
box-sizing: border-box;
}
&__name{
display: flex;
align-items: center;
text-transform: uppercase;
font-weight: 700;
font-size: 2.4rem;
flex: 1;
span{
text-align: center;
background: linear-gradient(left, $light 50%, rgba($light, 0.4) 50%) right;
background-size: 200%;
background-clip: text;
text-fill-color: transparent;
animation: fill $anim*100 forwards linear;
}
}
&__author{
position: absolute;
top: 2rem;
left: 0;
width: 100%;
text-align: center;
text-transform: none;
font-size: 1.6rem;
font-weight: 500;
text-fill-color: rgba($light, 0.6);
}
&__panel, &__actions{
.btn--play{
margin: 0;
color: $light;
background-color: transparent;
transition: $anim*2;
&:not(:disabled):hover{
background-color: rgba($light, 0.3);
}
}
}
&__panel{
display: flex;
justify-content: center;
width: 100%;
.btn--play{
height: 5rem;
width: 5rem;
i{
font-size: 2.4rem;
}
&:not(:last-child){
margin-right: 2rem;
}
}
}
&__actions{
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 1rem;
.btn--play{
opacity: 0;
animation-delay: $anim*3;
animation: horizontalShift forwards $anim*4;
i{
font-size: 1.8rem;
}
&:first-child{
transform: translateX(-100%);
}
&:last-child{
transform: translateX(100%);
}
}
}
&.simple{
transform: translateY(-8rem);
// transition: $anim*2;
// opacity: 0;
.song{
&__cover-wrapper{
transform: scale(0.8);
transition: $anim/3;
border-radius: 1.2rem;
flex: 0;
}
&__cover{
transform: scale(0.8);
transition: $anim/5;
border-radius: 50%;
}
&__info{
height: 10%;
flex: none;
flex-direction: row;
padding: 3rem 1.5rem 1rem;
}
&__name{
display: flex;
flex-direction: column;
align-items: flex-start;
cursor: pointer;
span{
text-transform: none;
background-color: $light;
font-size: 1.6rem;
animation: none;
line-height: 1.2;
}
}
&__author{
position: static;
text-align: left;
font-size: 1.2rem;
font-weight: 300;
}
&__panel{
width: auto;
.btn--play{
margin: 0;
height: 3.6rem;
width: 3.6rem;
i{
font-size: 1.4rem;
}
&.prev{
display: none;
}
}
}
}
.backward i{
transform: rotate(-180deg);
}
}
}
.backward{
cursor: pointer;
position: absolute;
top: -1rem;
right: 1rem;
width: 3rem;
height: 3rem;
border-radius: 1rem;
z-index: 3;
color: $accent;
background-color: $light;
transition: $anim;
box-shadow: 0 0 1rem rgba($accent, 0.2);
i{
font-size: 1.8rem;
transition: $anim;
}
}
@keyframes horizontalShift{
100%{
opacity: 1;
transform: translateX(0);
}
}
@keyframes scale{
100%{
opacity: 1;
transform: scale(1);
}
}
@keyframes fill{
100%{
background-position: left;
}
}
@keyframes popup{
0%{
opacity: 0;
}
100%{
opacity: 1;
}
}
@keyframes fullpopup{
100%{
transform: translateY(-94%);
}
}
View Compiled
const songList = [
{name:'Sanctified with Dynamite', author: 'PowerWolf', duration: '3:51', cover: 'https://cdnb.artstation.com/p/assets/images/images/010/532/065/large/zsofia-dankova-1.jpg?1539776234'},
{name:'Army of the Night', author: 'PowerWolf', duration: '3:51', cover: 'https://i.pinimg.com/originals/10/37/36/1037361b721513a7168e1dae07139f55.jpg'},
{name:'Higher Than Heaven', author: 'PowerWolf', duration: '3:51', cover: 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/b81633f7-ac45-4e1d-9255-46297d588240/dcf39re-204aaff0-a53f-4f9b-83d3-81239bf35778.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcL2I4MTYzM2Y3LWFjNDUtNGUxZC05MjU1LTQ2Mjk3ZDU4ODI0MFwvZGNmMzlyZS0yMDRhYWZmMC1hNTNmLTRmOWItODNkMy04MTIzOWJmMzU3NzgucG5nIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.fE1cp7I7GjauqJ8gKCoqz2cK1BHjeAwhivRnE7oMsVo'},
{name:'Incense & Iron', author: 'PowerWolf', duration: '3:51', cover: 'https://i.pinimg.com/originals/2b/ca/63/2bca632180d842a6f15908154ce862bb.jpg'},
{name:'Venom of Venus', author: 'PowerWolf', duration: '3:51', cover: 'https://steamuserimages-a.akamaihd.net/ugc/941709259346307842/830C554F58DDEF61ACD21D28FBC3FC4FEAAAE136/'},
{name:'Sanctified with Dynamite', author: 'PowerWolf', duration: '3:51', cover: 'https://cdnb.artstation.com/p/assets/images/images/010/532/065/large/zsofia-dankova-1.jpg?1539776234'},
{name:'Army of the Night', author: 'PowerWolf', duration: '3:51', cover: 'https://i.pinimg.com/originals/10/37/36/1037361b721513a7168e1dae07139f55.jpg'},
{name:'Higher Than Heaven', author: 'PowerWolf', duration: '3:51', cover: 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/b81633f7-ac45-4e1d-9255-46297d588240/dcf39re-204aaff0-a53f-4f9b-83d3-81239bf35778.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcL2I4MTYzM2Y3LWFjNDUtNGUxZC05MjU1LTQ2Mjk3ZDU4ODI0MFwvZGNmMzlyZS0yMDRhYWZmMC1hNTNmLTRmOWItODNkMy04MTIzOWJmMzU3NzgucG5nIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.fE1cp7I7GjauqJ8gKCoqz2cK1BHjeAwhivRnE7oMsVo'},
{name:'Incense & Iron', author: 'PowerWolf', duration: '3:51', cover: 'https://i.pinimg.com/originals/2b/ca/63/2bca632180d842a6f15908154ce862bb.jpg'},
{name:'Venom of Venus', author: 'PowerWolf', duration: '3:51', cover: 'https://steamuserimages-a.akamaihd.net/ugc/941709259346307842/830C554F58DDEF61ACD21D28FBC3FC4FEAAAE136/'},
]
const Btn = ({className = '', icon = 'play_arrow', children, props}) => (
<button className={`btn--play ${className}`} {props}>
{children
? children
: <i class={`fa fa-${icon}`}/>
}
</button>
)
const SongList = ({list = [], stop, handle, active, open}) => (
<div className='list'>
<h3 className='list__title'>
Songs
{(!active && active !== 0) &&
<Btn className='list__action' icon='play' onClick={() => stop(0)}/>
}
</h3>
<div className={`list__wrapper ${open ? 'bg': ''}`}>
{list.map((el,index) => (
<div className={`list__item ${active === index ? 'active' : ''}`} style={{transitionDelay: `${0.075 * index}s`}} onClick={() => stop(index)}>
<span className='list__cover' style={{transitionDelay: `${0.075 * index}s`}}>
<img src={el.cover}/>
</span>
<div className='info' >
<span className='info__name'>{el.name}</span>
<span className='info__author'>{el.author}</span>
</div>
<span className='info__duration' style={{transitionDelay: `${0.075 * index}s`}}>{el.duration}</span>
</div>
))}
</div>
</div>
)
const SongPage = ({index, stop, next, prev, open, handleOpen, pause}) => {
const data = songList[index];
return (
<div class={`song ${open ? '' : 'simple'}`}>
<Btn className='backward' icon='angle-down' onClick={handleOpen}/>
<div className='song__cover-wrapper'>
<div class="song__cover">
<img src={data.cover} alt="" />
</div>
{open &&
<div className='song__actions'>
<Btn className='song__btn' icon='random'/>
<Btn className='song__btn' icon='repeat'/>
</div>
}
</div>
<div class="song__info">
<span class="song__name" onClick={!open ? handleOpen : undefined}>
<span>{data.name}</span>
<span class="song__author">{data.author}</span>
</span>
<div class="song__panel">
{open && <Btn className='song__btn prev' icon='backward' disabled={!prev} onClick={prev}/>}
<Btn className='song__btn' icon={pause ? 'play' : 'pause'} onClick={() => stop(index)}/>
<Btn className='song__btn next' icon='forward' disabled={!next} onClick={next}/>
</div>
{!open && <span className='info__status'><span></span></span>}
</div>
</div>
)
};
class App extends React.Component{
state = {
open: false,
active: false,
pause: false
};
handleOpen = () => { this.setState(state => ({ open: !state.open }) )};
pause = () => {this.setState(state => ({ pause: !state.pause}) )}
handlePlay = (active) => {
this.setState(state => ({
active: state.active === active ? false : active
}))
};
next = () => this.handlePlay(this.state.active < songList.length - 1 ? this.state.active + 1 : 0);
prev = () => this.handlePlay(this.state.active > 0 ? this.state.active - 1 : songList.length - 1);
render(){
const {active, open, pause} = this.state;
return (
<div className='player'>
<SongList
list={songList}
stop={this.handlePlay}
open={open}
handle={this.handlePlay}
active={active}
/>
{(active || active === 0) &&
<SongPage
open={open}
index={active}
handleOpen={this.handleOpen}
pause={pause}
stop={this.pause}
next={this.next}
prev={this.prev}/>
}
</div>
)
}
};
ReactDOM.render(
<App/>,
document.getElementById('root'),
)
View Compiled
This Pen doesn't use any external CSS resources.