Spotify Demo
Easily implement custom functionality like media embeds and custom events within your grids.
Result
Full
HTML
CSS
JS
Edit
Download
Spotify Charts Daily Global Viral 50
No. [[index.position]]
[[record.trackName]]
Full Code
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ZingGrid: Blank Grid</title> <script nonce="undefined" src="https://cdn.zinggrid.com/zinggrid.min.js"></script> <style> /* Namespaced Body */ .zg-body { background: #000; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: 'Rubik', sans-serif; } /* Shared Defaults */ zg-caption, zg-head, zg-body { background: #000; } /* ZingGrid Element */ zing-grid { max-width: 900px; margin: 0 auto; border: 0; background: #000; font-family: 'Rubik', sans-serif; color: #ffffff; opacity: 1; --theme-color-primary: #1ed65f; --zg-select-arrow-color: #1ed65f; --zg-control-bar-background: #000; --zg-control-bar-border-bottom: #4b4a4a; --zg-head-background: #000; --zg-option-list-color: #000; } zing-grid.loading { opacity: 0; transition: opacity .3s ease-out; } zing-grid[loading], zing-grid[loading] zg-body { height: 700px; } /* Special styling when in 'row' mode */ zing-grid[layout="row"] { min-width: 700px; } /* Caption */ zg-caption h2 { color: #fff; font-weight: bold; font-size: 37px; margin: 0 0 -50px 0; border-bottom: 0px solid #333030; } zg-caption a { font-size: 17px; font-weight: normal; margin-left: 20px; color: #1ed65f; text-decoration: none; } zg-caption a:hover { color: #15a548; } zg-header[layout="card"] zg-caption { height: 5rem; } zg-header[layout="card"] zg-caption a { display: block; margin-left: 0; } /* Grid Head */ zg-head { border-bottom: 0px solid #4b4a4a; margin-bottom: 15px; } /* Grid Controls */ zg-control-bar { background: #000; } /* Grid Rows */ zg-row[layout="card"] { background: #181717; margin: 5px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-left: 1px solid #282626; border-top: 1px solid #282626; border-right: 1px solid #191818; border-bottom: 1px solid #191818; } zg-head zg-row[layout="card"] { display: none; } zg-head zg-row[layout="card"] zg-head-cell { display: none; } zg-body[layout="row"] zg-row { border-top: 1px solid #4b4a4a; border-bottom: 1px solid #4b4a4a; } /* Grid Cells */ .zg-head-cell-inner { border-bottom: 1px solid #4b4a4a; color: #1ed65f; } zg-cell:first-child { margin-top: -25px; font-weight: bold; color: #1ed65f; font-size: 12px; } zg-row[layout="row"] zg-cell:first-child p { white-space: nowrap; } zg-cell:nth-child(2) { margin-top: -30px; } zg-cell:nth-child(2) img { border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-left: 1px solid #191818; border-top: 1px solid #191818; border-right: 1px solid #353232; border-bottom: 1px solid #353232; margin: 0 auto; } zg-row[layout="row"] zg-cell:nth-child(2) img { width: 100px; border: 0; border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0; margin-bottom: -3px; } zg-cell:nth-child(3) { margin-top: -25px; font-size: 25px; font-weight: bold; } zg-row[layout="row"] zg-cell:nth-child(3) { margin-top: -25px; font-size: 20px; } zg-cell:nth-child(4) { font-size: 15px; margin-top: -30px; } zg-cell:last-child { padding-bottom: 10px; } zg-row[layout="card"] video { width: 100%; margin-top: -80px; } zg-row[layout="row"] video { margin-top: -80px; } /* Album Covers */ .image--container { position: relative; } .image--container img { width: 100%; } .image--container [data-type] { position: absolute; opacity: 0; top: calc(50% - 20px); left: calc(50% - 20px); } /* Currently Playing */ .currently-playing { position: absolute; margin-top: -20px; font-size: 11px; color: #62D26D; opacity: 0; } /* Styling for Media 'State' Controls */ zg-body zg-row:hover .image--container [data-type="play-button"], zg-body zg-row[playing]:hover .image--container [data-type="pause-button"], zg-body zg-row[layout="row"][playing] [data-field-index="trackName"] .currently-playing, zg-body zg-row[layout="card"][playing] [data-field-index="position"] .currently-playing { opacity: 1; } zg-body zg-row[playing]:hover .image--container [data-type="play-button"] { opacity: 0; } zg-body zg-row[layout="card"][playing] [data-field-index="position"] .currently-playing { right: 17px; margin-top: 11px; } /* Row/card Border Styling */ zg-body zg-row { border: 2px solid transparent; } zg-body zg-row:hover { border: 2px solid #62D26D; background: rgba(22, 62, 26, 0.5); } zg-body zg-row[playing] { background: rgba(62, 62, 62, 0.5); } zg-body zg-row[layout="card"][playing] { border: 2px solid #62D26D; } /* Pager */ zg-pager { border-top: 0; padding-top: 10px; } /* MEDIA QUERIES (MAX WIDTH) ---------------------------------------- */ /* BELOW TABLET: PORTRAIT */ @media (max-width: 766px) { zing-grid[layout="card"] { max-width: 400px; } } zing-grid[loading] { height: 698px; } </style> </head> <body class="zg-body"> <link href="https://fonts.googleapis.com/css?family=Rubik:400,700" rel="stylesheet"> <zing-grid src="https://zinggrid-examples.firebaseio.com/spotify-demo" layout-controls loading pager page-size="6" page-size-options="6,12,24,50" class="loading"> <zg-caption> <h2>Spotify Charts <a href="https://spotifycharts.com/viral/global/daily/latest" target="_blank" crossorigin>Daily Global Viral 50</a></h2> </zg-caption> <zg-colgroup> <zg-column index="position" type="number" header=" "> <span class="currently-playing"> <b>Currently Playing</b> <svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M10 0.454846V8.47925C10 8.47925 9.96875 9.85379 8.254 9.85379C6.3765 9.85379 6.25 8.73669 6.25 8.47833C6.25 7.92586 6.4855 7.32364 8.25 7.32364C9.06225 7.32364 9.25 7.13222 9.25 6.64439C9.25 6.64439 9.25 3.09962 9.25 2.7977C9.25 2.49579 9.23 2.38667 8.86425 2.46255C8.235 2.59299 4.125 3.41001 3.961 3.446C3.797 3.48199 3.75 3.57117 3.75 3.83617C3.75 4.32377 3.75 9.10806 3.75 9.62455C3.75 10.141 3.3145 11 1.75 11C0.1855 11 0 10.0835 0 9.62455C0 8.94301 0.5625 8.50332 1.75175 8.50332C2.8595 8.50332 3 8.217 3 7.79061C3 6.27004 3 1.82113 3 1.60106C3 1.38099 3.09775 1.23221 3.3415 1.18292C3.74925 1.10085 9.5365 0.00667707 9.5365 0.00667707C9.5365 0.00667707 10 -0.0902925 10 0.454846Z" fill="#62D26D" /> </svg> </span> <p>No. [[index.position]]</p> </zg-column> <zg-column index="image" type="image" header=" " width=100> <div class="image--container"> <img src="[[index.image]]" alt="Album Art"> <div class="video-action--container"> <span data-type="play-button"> <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M20 0C8.95 0 0 8.95 0 20C0 31.05 8.95 40 20 40C31.05 40 40 31.05 40 20C40 8.95 31.05 0 20 0ZM16 29V11L28 20L16 29Z" fill="white" /> </svg> </span> <span data-type="pause-button"> <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M20 0C9 0 0 9 0 20C0 31 9 40 20 40C31 40 40 31 40 20C40 9 31 0 20 0ZM18 28H14V12H18V28ZM26 28H22V12H26V28Z" fill="white" /> </svg> </span> </div> </div> </zg-column> <zg-column index="trackName" header=" "> <span class="currently-playing"> <b>Currently Playing</b> <svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M10 0.454846V8.47925C10 8.47925 9.96875 9.85379 8.254 9.85379C6.3765 9.85379 6.25 8.73669 6.25 8.47833C6.25 7.92586 6.4855 7.32364 8.25 7.32364C9.06225 7.32364 9.25 7.13222 9.25 6.64439C9.25 6.64439 9.25 3.09962 9.25 2.7977C9.25 2.49579 9.23 2.38667 8.86425 2.46255C8.235 2.59299 4.125 3.41001 3.961 3.446C3.797 3.48199 3.75 3.57117 3.75 3.83617C3.75 4.32377 3.75 9.10806 3.75 9.62455C3.75 10.141 3.3145 11 1.75 11C0.1855 11 0 10.0835 0 9.62455C0 8.94301 0.5625 8.50332 1.75175 8.50332C2.8595 8.50332 3 8.217 3 7.79061C3 6.27004 3 1.82113 3 1.60106C3 1.38099 3.09775 1.23221 3.3415 1.18292C3.74925 1.10085 9.5365 0.00667707 9.5365 0.00667707C9.5365 0.00667707 10 -0.0902925 10 0.454846Z" fill="#62D26D" /> </svg> </span> <div>[[record.trackName]]</div> </zg-column> <zg-column index="artist" header=" "></zg-column> <zg-column index="url" renderer="video" header=" " hidden> <template> <video controls name="media"> <source src="[[index.url]]" type="audio/mpeg"> </video> </template> </zg-column> </zg-colgroup> </zing-grid> <script> ZingGrid.setLicense(['26ccbfec16b8be9ee98c7d57bee6e498']); // EVENTS // --------------------------- // window:load event for Javascript to run after HTML // because this Javascript is injected into the document head window.addEventListener('load', () => { // Javascript code to execute after DOM content const zgRef = document.querySelector('zing-grid'); // handle playing the songs zgRef.addEventListener('row:click', _handleRowClick); zgRef.addEventListener('card:click', _handleRowClick); // handle changing layouts to keep playing track }); // Custom loading class for CSS handling const zgLoaded = document.querySelector('zing-grid'); zgLoaded.addEventListener('grid:ready', () => { setTimeout(() => zgLoaded.classList.remove('loading'), 0); }); // HELPER FNS // --------------------------- // Event handler when media has stopped (to remove 'playing' state) function handleMediaEnded(rowRef) { rowRef.removeAttribute('playing'); } // Render the video markup function video(index, cellDOMRef, cellRef) { const videoRef = cellDOMRef.querySelector('video'); const rowRef = cellDOMRef.parentNode; const mediaEventHandler = handleMediaEnded.bind(null, rowRef); // Attach event listeners for when the media ends videoRef.addEventListener('ended', mediaEventHandler); // Remove active state if row already playing (caused by layout switch) if (rowRef.hasAttribute('playing')) rowRef.removeAttribute('playing'); } // Handle row/card click to start/stop a video function _handleRowClick(e) { const rowDetails = e.detail.ZGTarget; const videoRef = rowDetails.querySelector('video'); const allRows = document.querySelectorAll('zg-body zg-row'); // pause all other videos [...allRows].forEach(row => { const video = row.querySelector('video'); // skip currently playing video if (videoRef == video) return; // pause all other videos // and remove active state if (!video.paused) { video.pause(); row.removeAttribute('playing'); } }); // if we are playing if (videoRef) { if (videoRef.paused) { videoRef.play(); rowDetails.setAttribute('playing', 'true'); } else { videoRef.pause(); rowDetails.removeAttribute('playing'); } } } </script> </body> </html>
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ZingGrid: Blank Grid</title> <script src="https://cdn.zinggrid.com/zinggrid.min.js"></script> </head> <body class="zg-body"> <link href="https://fonts.googleapis.com/css?family=Rubik:400,700" rel="stylesheet"> <zing-grid src="https://zinggrid-examples.firebaseio.com/spotify-demo" layout-controls loading pager page-size="6" page-size-options="6,12,24,50" class="loading" > <zg-caption> <h2>Spotify Charts <a href="https://spotifycharts.com/viral/global/daily/latest" target="_blank" crossorigin>Daily Global Viral 50</a></h2> </zg-caption> <zg-colgroup> <zg-column index="position" type="number" header=" "> <span class="currently-playing"> <b>Currently Playing</b> <svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M10 0.454846V8.47925C10 8.47925 9.96875 9.85379 8.254 9.85379C6.3765 9.85379 6.25 8.73669 6.25 8.47833C6.25 7.92586 6.4855 7.32364 8.25 7.32364C9.06225 7.32364 9.25 7.13222 9.25 6.64439C9.25 6.64439 9.25 3.09962 9.25 2.7977C9.25 2.49579 9.23 2.38667 8.86425 2.46255C8.235 2.59299 4.125 3.41001 3.961 3.446C3.797 3.48199 3.75 3.57117 3.75 3.83617C3.75 4.32377 3.75 9.10806 3.75 9.62455C3.75 10.141 3.3145 11 1.75 11C0.1855 11 0 10.0835 0 9.62455C0 8.94301 0.5625 8.50332 1.75175 8.50332C2.8595 8.50332 3 8.217 3 7.79061C3 6.27004 3 1.82113 3 1.60106C3 1.38099 3.09775 1.23221 3.3415 1.18292C3.74925 1.10085 9.5365 0.00667707 9.5365 0.00667707C9.5365 0.00667707 10 -0.0902925 10 0.454846Z" fill="#62D26D"/> </svg> </span> <p>No. [[index.position]]</p> </zg-column> <zg-column index="image" type="image" header=" " width=100> <div class="image--container"> <img src="[[index.image]]" alt="Album Art"> <div class="video-action--container"> <span data-type="play-button"> <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M20 0C8.95 0 0 8.95 0 20C0 31.05 8.95 40 20 40C31.05 40 40 31.05 40 20C40 8.95 31.05 0 20 0ZM16 29V11L28 20L16 29Z" fill="white"/> </svg> </span> <span data-type="pause-button"> <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M20 0C9 0 0 9 0 20C0 31 9 40 20 40C31 40 40 31 40 20C40 9 31 0 20 0ZM18 28H14V12H18V28ZM26 28H22V12H26V28Z" fill="white"/> </svg> </span> </div> </div> </zg-column> <zg-column index="trackName" header=" "> <span class="currently-playing"> <b>Currently Playing</b> <svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M10 0.454846V8.47925C10 8.47925 9.96875 9.85379 8.254 9.85379C6.3765 9.85379 6.25 8.73669 6.25 8.47833C6.25 7.92586 6.4855 7.32364 8.25 7.32364C9.06225 7.32364 9.25 7.13222 9.25 6.64439C9.25 6.64439 9.25 3.09962 9.25 2.7977C9.25 2.49579 9.23 2.38667 8.86425 2.46255C8.235 2.59299 4.125 3.41001 3.961 3.446C3.797 3.48199 3.75 3.57117 3.75 3.83617C3.75 4.32377 3.75 9.10806 3.75 9.62455C3.75 10.141 3.3145 11 1.75 11C0.1855 11 0 10.0835 0 9.62455C0 8.94301 0.5625 8.50332 1.75175 8.50332C2.8595 8.50332 3 8.217 3 7.79061C3 6.27004 3 1.82113 3 1.60106C3 1.38099 3.09775 1.23221 3.3415 1.18292C3.74925 1.10085 9.5365 0.00667707 9.5365 0.00667707C9.5365 0.00667707 10 -0.0902925 10 0.454846Z" fill="#62D26D"/> </svg> </span> <div>[[record.trackName]]</div> </zg-column> <zg-column index="artist" header=" "></zg-column> <zg-column index="url" renderer="video" header=" " hidden> <template> <video controls name="media"> <source src="[[index.url]]" type="audio/mpeg"> </video> </template> </zg-column> </zg-colgroup> </zing-grid> </body> </html>
/* Namespaced Body */ .zg-body { background:#000; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: 'Rubik', sans-serif; } /* Shared Defaults */ zg-caption, zg-head, zg-body { background: #000; } /* ZingGrid Element */ zing-grid { max-width: 900px; margin: 0 auto; border: 0; background: #000; font-family: 'Rubik', sans-serif; color: #ffffff; opacity: 1; --theme-color-primary: #1ed65f; --zg-select-arrow-color: #1ed65f; --zg-control-bar-background: #000; --zg-control-bar-border-bottom: #4b4a4a; --zg-head-background: #000; --zg-option-list-color: #000; } zing-grid.loading { opacity:0; transition:opacity .3s ease-out; } zing-grid[loading], zing-grid[loading] zg-body { height:700px; } /* Special styling when in 'row' mode */ zing-grid[layout="row"] { min-width:700px; } /* Caption */ zg-caption h2 { color: #fff; font-weight: bold; font-size: 37px; margin: 0 0 -50px 0; border-bottom: 0px solid #333030; } zg-caption a { font-size: 17px; font-weight: normal; margin-left: 20px; color: #1ed65f; text-decoration: none; } zg-caption a:hover { color: #15a548; } zg-header[layout="card"] zg-caption { height: 5rem; } zg-header[layout="card"] zg-caption a { display: block; margin-left: 0; } /* Grid Head */ zg-head { border-bottom: 0px solid #4b4a4a; margin-bottom: 15px; } /* Grid Controls */ zg-control-bar { background: #000; } /* Grid Rows */ zg-row[layout="card"] { background: #181717; margin: 5px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-left: 1px solid #282626; border-top: 1px solid #282626; border-right: 1px solid #191818; border-bottom: 1px solid #191818; } zg-head zg-row[layout="card"] { display: none; } zg-head zg-row[layout="card"] zg-head-cell { display: none; } zg-body[layout="row"] zg-row { border-top: 1px solid #4b4a4a; border-bottom: 1px solid #4b4a4a; } /* Grid Cells */ .zg-head-cell-inner { border-bottom: 1px solid #4b4a4a; color: #1ed65f; } zg-cell:first-child { margin-top: -25px; font-weight: bold; color: #1ed65f; font-size: 12px; } zg-row[layout="row"] zg-cell:first-child p { white-space: nowrap; } zg-cell:nth-child(2) { margin-top: -30px; } zg-cell:nth-child(2) img { border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-left: 1px solid #191818; border-top: 1px solid #191818; border-right: 1px solid #353232; border-bottom: 1px solid #353232; margin: 0 auto; } zg-row[layout="row"] zg-cell:nth-child(2) img { width: 100px; border: 0; border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0; margin-bottom: -3px; } zg-cell:nth-child(3) { margin-top: -25px; font-size: 25px; font-weight: bold; } zg-row[layout="row"] zg-cell:nth-child(3) { margin-top: -25px; font-size: 20px; } zg-cell:nth-child(4) { font-size: 15px; margin-top: -30px; } zg-cell:last-child { padding-bottom: 10px; } zg-row[layout="card"] video { width: 100%; margin-top: -80px; } zg-row[layout="row"] video { margin-top: -80px; } /* Album Covers */ .image--container { position:relative; } .image--container img { width:100%; } .image--container [data-type] { position:absolute; opacity:0; top: calc(50% - 20px); left: calc(50% - 20px); } /* Currently Playing */ .currently-playing { position:absolute; margin-top: -20px; font-size: 11px; color: #62D26D; opacity: 0; } /* Styling for Media 'State' Controls */ zg-body zg-row:hover .image--container [data-type="play-button"], zg-body zg-row[playing]:hover .image--container [data-type="pause-button"], zg-body zg-row[layout="row"][playing] [data-field-index="trackName"] .currently-playing, zg-body zg-row[layout="card"][playing] [data-field-index="position"] .currently-playing { opacity:1; } zg-body zg-row[playing]:hover .image--container [data-type="play-button"] { opacity:0; } zg-body zg-row[layout="card"][playing] [data-field-index="position"] .currently-playing { right:17px; margin-top:11px; } /* Row/card Border Styling */ zg-body zg-row { border:2px solid transparent; } zg-body zg-row:hover { border:2px solid #62D26D; background: rgba(22, 62, 26, 0.5); } zg-body zg-row[playing] { background: rgba(62, 62, 62, 0.5); } zg-body zg-row[layout="card"][playing] { border:2px solid #62D26D; } /* Pager */ zg-pager { border-top: 0; padding-top: 10px; } /* MEDIA QUERIES (MAX WIDTH) ---------------------------------------- */ /* BELOW TABLET: PORTRAIT */ @media (max-width: 766px) { zing-grid[layout="card"] { max-width: 400px; } }
// EVENTS // --------------------------- // window:load event for Javascript to run after HTML // because this Javascript is injected into the document head window.addEventListener('load', () => { // Javascript code to execute after DOM content const zgRef = document.querySelector('zing-grid'); // handle playing the songs zgRef.addEventListener('row:click', _handleRowClick); zgRef.addEventListener('card:click', _handleRowClick); // handle changing layouts to keep playing track }); // Custom loading class for CSS handling const zgLoaded = document.querySelector('zing-grid'); zgLoaded.addEventListener('grid:ready', () => { setTimeout(() => zgLoaded.classList.remove('loading'), 0); }); // HELPER FNS // --------------------------- // Event handler when media has stopped (to remove 'playing' state) function handleMediaEnded(rowRef) { rowRef.removeAttribute('playing'); } // Render the video markup function video(index, cellDOMRef, cellRef) { const videoRef = cellDOMRef.querySelector('video'); const rowRef = cellDOMRef.parentNode; const mediaEventHandler = handleMediaEnded.bind(null, rowRef); // Attach event listeners for when the media ends videoRef.addEventListener('ended', mediaEventHandler); // Remove active state if row already playing (caused by layout switch) if (rowRef.hasAttribute('playing')) rowRef.removeAttribute('playing'); } // Handle row/card click to start/stop a video function _handleRowClick(e) { const rowDetails = e.detail.ZGTarget; const videoRef = rowDetails.querySelector('video'); const allRows = document.querySelectorAll('zg-body zg-row'); // pause all other videos [...allRows].forEach(row => { const video = row.querySelector('video'); // skip currently playing video if (videoRef == video) return; // pause all other videos // and remove active state if (!video.paused) { video.pause(); row.removeAttribute('playing'); } }); // if we are playing if (videoRef) { if (videoRef.paused) { videoRef.play(); rowDetails.setAttribute('playing', 'true'); } else { videoRef.pause(); rowDetails.removeAttribute('playing'); } } }
Interested in this demo? Modify it to your needs in ZingSoft Studio, our testing sandbox. It's free to sign up, and you can come back and edit at any time!