HTML
<div class="main inactive">
<div class="bg-main"></div>
<section class="search">
<div class="search-box">
<div class="introduction-text">
<h1>Search over 200 million songs.</h1>
<p>Know what you want to listen to? Just search and hit play.</p>
</div>
<div class="cancel"></div>
<form class="search-form">
<div class="query-box">
<input class="query-input" type="text" autocomplete="off" name="search" placeholder="e.g. 'foals', 'queen'">
<div id="results">
</div>
</div>
</form>
</div>
</section>
</div>
CSS
/* responsive down to mobile */
/* implementation based on original concept design by Clément Goebels */
/* https://www.behance.net/gallery/33015995/Spotify-UI-redesign-concept */
body {
margin: 0em;
padding: 0;
font-family: 'CircularBook', Helvetica, Arial, sans-serif;
background-size: 100% 100%;
}
.bg-main {
background: url(https://samratcliffe.github.io/images/spotifybg.png) no-repeat;
background-size: cover;
width: 100%;
height: 100%;
top: 0;
left: 0;
position: fixed;
z-index: -1;
}
.main {}
.search {
width: 97%;
max-width: 1280px;
margin: 0 auto;
text-align: center;
}
.query-box {
position: relative;
z-index: 1;
}
.search-box {
color: #fff;
}
.inactive .introduction-text {
max-height: 99em;
opacity: 1;
}
.introduction-text {
max-height: 0em;
opacity: 0;
transition: max-height 0.3s, opacity 0.3s;
}
.search-box h1 {
font-family: "CircularBlack";
font-size: 3.5em;
margin: 1em 0 0 0;
line-height: 1;
font-weight: normal;
}
.cancel {
display: inline-block;
vertical-align: top;
width: 4.7em;
height: 4.7em;
margin-top: 1em;
cursor: pointer;
opacity: 1;
visibility: visible;
background: url(https://samratcliffe.github.io/images/cancel.svg) no-repeat;
background-size: contain;
transform: translate(0em, 0);
transition-duration: 0.3s;
transition-delay: 0s;
}
.inactive .cancel {
opacity: 0;
visibility: hidden;
width: 0px;
transition-duration: 0.3s;
transition-delay: 0s;
transform: translate(2em, 0);
}
.search-form {
display: inline-block;
vertical-align: top;
width: calc(100% - 7em);
;
}
.search-form input.inactive {
border: 1px solid #fff;
font-size: 1.25em;
max-width: 18em;
padding: 0.6em;
}
.main.inactive .search-form input {
border: 1px solid #fff;
font-size: 1.25em;
max-width: 18em;
padding: 0.6em;
}
.search-form input {
box-sizing: border-box;
font-family: 'CircularLight', Helvetica, Arial, sans-serif;
width: 100%;
max-width: 100%;
font-size: 5.5em;
background: transparent;
transition: all 0.3s;
padding: 0 0.2em;
color: #fff;
border: solid 1px transparent;
border-bottom: solid 1px #666;
margin-bottom: 0.1em;
outline: none;
}
.search-form #results {
text-align: left;
transform: translate(0, -1.5em);
overflow-y: auto;
transition: transform 0.2s, opacity 0.2s, visibility 0s 0.3s;
z-index: -1;
opacity: 1;
position: relative;
display: flex;
align-items: flex-start;
visibility: hidden;
}
.search-form #results.active {
opacity: 1;
visibility: visible;
transform: translate(0, 0em);
transition-delay: 0s;
}
#results ul {
margin: 0;
position: relative;
padding: 0em 0.7em 0.5em 0.7em;
color: #fff;
min-height: 40em;
flex-basis: 24.5%;
flex-grow: 1;
opacity: 0;
transition: opacity 0.3s;
}
#results.active ul {
opacity: 1;
}
#results ul:nth-of-type(4) {
transition-delay: 0.2s;
}
#results ul:nth-of-type(3) {
transition-delay: 0.15s;
}
#results ul:nth-of-type(2) {
transition-delay: 0.1s;
}
#results ul:nth-of-type(1) {
transition-delay: 0.05s;
}
#results ul div div {
display: inline-block;
width: 49%;
margin: 0;
text-transform: uppercase;
letter-spacing: 0.2em;
padding: 1em 0 0.5em 0;
font-size: 0.75em;
}
#results ul div .title-wrap {
color: #888;
}
#results ul div .show-more-wrap {
text-align: right;
}
#results ul div .show-more-wrap a {
display: inline-block;
border: 1px solid #666;
color: #ddd;
text-decoration: none;
padding: 0.7em 1.5em;
border-radius: 1.8em;
text-align: center;
vertical-align: middle;
font-size: 0.8em;
letter-spacing: 0.3em;
background: rgba(255, 255, 255, 0);
}
#results ul div .show-more-wrap a:hover {
border-color: #fff;
}
#results ul li:hover {
background: rgba(255, 255, 255, 0.05);
}
#results ul li {
list-style-type: none;
position: relative;
line-height: 1;
padding: 0.3em;
margin: 0 0 2px 0;
transition: background 0.2s;
}
#results ul li>div {
padding: 0.3em;
box-sizing: border-box;
width: calc(70% - 0.5em);
}
#results ul li>figure {
width: 20%;
margin-right: 0.5em;
}
#results ul li>figure img {
width: 100%;
}
#results ul li:last-child {
margin-bottom: 0;
}
#results.active ul li {
opacity: 1;
transform: translate(0, 0em) rotateX(0deg);
}
#results li > figure,
#results li > div {
display: inline-block;
vertical-align: middle;
margin: 0;
}
#results ul li p {
margin: 0;
padding: 0.2em 0;
line-height: 1.2;
}
#results ul li>a {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
#results ul li a {
text-decoration: none;
color: inherit;
}
#results ul li a:hover {
text-decoration: underline;
}
#results ul.artists li:first-of-type .item-title {
font-size: 1.375em;
}
p.item-title {
font-family: "CircularBook";
}
#results ul.artists li:first-of-type .item-title a {
color: #fff;
}
p.item-subtitle {
color: #666;
font-family: "CircularLight";
text-transform: capitalize;
}
/* Artists */
#results ul.artists li figure div {
width: 100%;
padding-bottom: 100%;
height: 0;
position: relative;
}
#results ul.artists li {
margin-bottom: 1.5em;
}
#results ul.artists li:first-of-type figure {
width: 27%;
}
#results ul.artists li>figure div img {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border-radius: 50%;
}
/*Playlists */
#results ul.playlists li figure {
display: none;
}
#results ul.playlists {
flex-basis: 20%;
}
#results ul.playlists div .show-more-wrap a {
display: none;
}
/*Tracks*/
#results ul.tracks {
flex-basis: 30%;
}
#results ul.tracks li>div>a {
width: 2em;
display: inline-block;
height: 2em;
border: 1px solid #aaa;
position: absolute;
border-radius: 50%;
right: 0.5em;
top: 50%;
opacity: 0;
transform: translate(0%, -50%);
transition: opacity 0.3s, background-color 0.2s;
background: url(https://samratcliffe.github.io/images/play.svg) no-repeat;
background-color: rgba(255, 255, 255, 0.0);
background-size: 1em 1em;
background-position: 58% 50%;
}
#results ul.tracks li:hover>div>a {
opacity: 1;
transition-delay: 0.2s;
}
#results ul.tracks li>div>a:hover {
background-color: rgba(255, 255, 255, 0.1);
transition-delay: 0s;
}
#results ul.tracks li {
padding-right: 2.3em;
}
/*Albums */
#results ul.albums li {
position: absolute;
transition: transform 0.3s;
transform-origin: top;
width: 84%;
left: 8%;
box-sizing: border-box;
font-size: 1.25em;
}
#results ul.albums li figure {
width: 100%;
}
#results ul.albums li:nth-of-type(1) {
transform: scale(0.8);
margin-top: 0;
}
#results ul.albums li:nth-of-type(2) {
transform: scale(0.9);
margin-top: 2em;
}
#results ul.albums li:nth-of-type(3) {
transform: scale(1);
margin-top: 4em;
}
#results ul.albums li:not(:last-of-type) p {
display: none;
}
@media only screen and (max-width:1024px) {
#results ul.playlists {
display:none;
}
}
@media only screen and (max-width:768px) {
#results {
flex-flow: column;
}
#results ul {
width:100%;
box-sizing:border-box;
min-height:auto;
}
#results ul.albums {
display:none;
}
}
JAVASCRIPT
/* Functional, Memoized, cached, and object-oriented spotify search. written in vanilla js.
*/
function searchLibrary(query) {
//if the result is cached, return it.
searchLibrary.cache = searchLibrary.cache || {};
if (searchLibrary.cache[encodeURIComponent(query)]) {
return writeResults(searchLibrary.cache[encodeURIComponent(query)]);
}
//otherwise create an ajax promise
makeNetworkRequest(query).then(function(response) {
searchLibrary.cache[encodeURIComponent(query)] = response;
writeResults(response);
}, function(e) {
console.error(e)
});
}
function writeResults(results) {
var output = document.getElementById("results");
output.innerHTML = "";
for (var resultType in JSON.parse(results)) {
var items = JSON.parse(results)[resultType].items;
var update = new resultList(resultType, items).render();
var domElement = document.getElementsByClassName(resultType);
output.appendChild(update);
}
}
function makeNetworkRequest(query) {
return new Promise(function(resolve, reject) {
var API_URL = "https://api.spotify.com/v1/search?limit=3&type=artist,track,album,playlist&q=" + encodeURI(query);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
resolve(xhr.responseText);
}
}
xhr.onerror = function() {
reject(new Error("network error"))
}
xhr.open("GET", API_URL, true);
xhr.send();
});
}
function listItem() {
this.render = function() {
var li = document.createElement("li");
var divImage = document.createElement("figure");
var divImageWrap = document.createElement("div");
var divDesc = document.createElement("div");
var img = document.createElement("img");
var title = document.createElement("p");
var subtitle = document.createElement("p");
var anchor = document.createElement("a");
var imgAnchor = document.createElement("a");
img.src = this.src === 0 ? "https://samratcliffe.github.io/images/placeholder.jpg" : this.src;
anchor.href = imgAnchor.href = this.href;
anchor.target = imgAnchor.target = "_blank";
title.classList.add("item-title");
subtitle.classList.add("item-subtitle");
title.innerHTML = this.title;
subtitle.innerHTML = this.subtitle;
imgAnchor.appendChild(img);
divImageWrap.appendChild(imgAnchor);
divDesc.appendChild(title);
divDesc.appendChild(subtitle);
divDesc.appendChild(anchor);
divImage.appendChild(divImageWrap);
li.appendChild(divImage);
li.appendChild(divDesc);
return li;
}
}
function resultListItem(resultItem) {
this.title = "<a target='_blank' href='" + resultItem.external_urls.spotify + "'>" + (resultItem.name.length > 45 ? resultItem.name.substr(0, 45) + "…" : resultItem.name) + "</a>";
this.href = resultItem.external_urls.spotify;
switch (resultItem.type) {
case "artist":
this.subtitle = Number(resultItem.followers.total).toLocaleString() + " listeners";
this.src = resultItem.images.length && resultItem.images.slice(-1)[0].url;
break;
case "album":
this.subtitle = resultItem.album_type;
this.src = resultItem.images.length && resultItem.images.slice(1)[0].url;
break;
case "track":
this.subtitle = "<a target='_blank' href='" + resultItem.artists[0].external_urls.spotify + "'>" + resultItem.artists[0].name + "</a>" + " • " + "<a target='_blank' href='" + resultItem.album.external_urls.spotify + "'>" + resultItem.album.name + "</a>";
this.src = resultItem.album.images.length && resultItem.album.images.slice(-1)[0].url;
break;
case "playlist":
this.subtitle = resultItem.type;
this.src = 0;
break;
default:
this.subtitle = resultItem.type;
break;
}
}
resultListItem.prototype = new listItem();
function resultList(name, results) {
this.name = name;
this.results = results;
//render
this.render = function() {
var ul = document.createElement("ul");
var headerWrap = document.createElement("div");
var titleWrap = document.createElement("div");
var showMoreWrap = document.createElement("div");
titleWrap.classList.add("title-wrap");
showMoreWrap.classList.add("show-more-wrap");
ul.classList.add(this.name);
var showMore = document.createElement("a");
showMore.innerHTML = "SHOW MORE";
showMore.href = "javascript:void(0)";
var header = document.createElement("p");
header.innerHTML = this.name;
titleWrap.appendChild(header);
showMoreWrap.appendChild(showMore);
headerWrap.appendChild(titleWrap);
headerWrap.appendChild(showMoreWrap);
ul.appendChild(headerWrap)
this.results.map(function(item) {
var li = new resultListItem(item);
ul.appendChild(li.render());
});
return ul;
}
}
var search = document.getElementsByClassName("query-input")[0];
search.addEventListener("keyup", function(e) {
var query = e.target.value;
var results = document.getElementById("results");
results.classList = query === "" ? "" : "active";
searchLibrary(query);
});
search.addEventListener("focus", function(e) {
var main = document.getElementsByClassName("main")[0];
main.classList = "main";
});
//cancel on X click or ESC
var cancel = document.getElementsByClassName("cancel")[0];
cancel.addEventListener("click", cancelInput);
document.onkeydown = function(e) {
e = e || window.event;
var isEscape = false;
if ("key" in e) {
isEscape = e.key == "Escape";
} else {
isEscape = e.keyCode == 27;
}
if (isEscape) cancelInput();
};
function cancelInput() {
var main = document.getElementsByClassName("main")[0];
main.classList.add("inactive");
search.value = '';
document.getElementById("results").classList = "";
search.blur();
}