Skip to content

Instantly share code, notes, and snippets.

@kkcoms
Created March 27, 2022 03:08
Show Gist options
  • Select an option

  • Save kkcoms/25eb20d402aa4fc5d6eb0853df27c363 to your computer and use it in GitHub Desktop.

Select an option

Save kkcoms/25eb20d402aa4fc5d6eb0853df27c363 to your computer and use it in GitHub Desktop.
Retro .gif TV (ES6)
<section class="row gutter">
<div class="wrapper">
<div class="gif-tv">
<div id="gif_tv_viewport" class="viewport">
<img id="gif_tv_video" class="video" src="/video-url-goes-here/">
<div id="gif_tv_pixels" class="pixels" style="background-image: url('https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571119227/vhs-overlay_zpzs7x.png')"></div>
<div class="meta-left">
<span id="gif_tv_message_channel"></span>
<span id="gif_tv_message_hd" class="small-message"></span>
<span id="gif_tv_message_hue_shift" class="small-message"></span>
<span id="gif_tv_message_bright" class="small-message"></span>
<span id="gif_tv_message_color" class="small-message"></span>
</div>
<div class="meta-right">
<span id="gif_tv_message_volume"></span>
<span id="gif_tv_message_mute" class="small-message"></span>
</div>
</div>
<img class="tv" src="https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571119227/80s-tv_ekkex2.png">
<button id="gif_tv_button_channel" class="dial" title="Change the Channels"></button>
<button id="gif_tv_button_volume" class="dial" title="Mute / Unmute"></button>
<button id="gif_tv_button_mute" class="switch" title="Mute / Unmute"></button>
<button id="gif_tv_button_hd" class="switch" title="High Def"></button>
<button id="gif_tv_button_hue_shift" class="switch" title="Hue Shift"></button>
<button id="gif_tv_button_bright" class="switch" title="Bright / Dark"></button>
<button id="gif_tv_button_color" class="switch" title="Color / B&W"></button>
<span class="cta">Push the Buttons!</span>
</div>
<div class="heading"><h1>GIF TV</h1></div>
</div>
</section>
const gifTVURLs = [
'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571117878/trippy-square_jqupb3.gif',
'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571117878/space-stallions_zmueag.gif',
'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571117882/dancing-bears-small_v4oqvi.gif',
'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571117881/trippy-rick_a42hyj.gif',
'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571117882/psychedelic-reindeer_an5vsi.gif',
'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571117877/jake-the-dog_scm4bi.gif',
'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571120920/the-regular-show_pwt1gp.gif',
]
class GifTV
{
constructor( channels = ['https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571155222/giphy_n0r827.gif'] )
{
// elements
this.gifVideo = document.getElementById('gif_tv_video');
this.pixels = document.getElementById('gif_tv_pixels');
this.viewport = document.getElementById('gif_tv_viewport');
// data
this.channels = channels;
this.staticGIF = 'https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571155222/giphy_n0r827.gif';
this.currentChannelURL = this.channels[0];
// sounds
this.sound = {
static: 'https://freesound.org/data/previews/41/41029_410502-lq.mp3',
dial: 'https://freesound.org/data/previews/485/485486_10145800-lq.mp3',
switch: 'https://freesound.org/data/previews/219/219477_4056007-lq.mp3',
}
// dials
this.dial = {
// Channels Dial
channel: {
button: document.getElementById('gif_tv_button_channel'),
currentIndex: 0,
message: document.getElementById('gif_tv_message_channel'),
messageTimer: null,
},
// Volume Dial
volume: {
button: document.getElementById('gif_tv_button_volume'),
currentIndex: 8,
message: document.getElementById('gif_tv_message_volume'),
messageTimer: null,
},
}
// switches
this.switch = {
// Mute Switch
mute: {
button: document.getElementById('gif_tv_button_mute'),
isActive: false,
message: document.getElementById('gif_tv_message_mute'),
messageTimer: null,
},
// HD Switch
hd: {
button: document.getElementById('gif_tv_button_hd'),
isActive: true,
message: document.getElementById('gif_tv_message_hd'),
messageTimer: null,
},
// Hue Shift Switch
hue: {
button: document.getElementById('gif_tv_button_hue_shift'),
isActive: false,
message: document.getElementById('gif_tv_message_hue_shift'),
messageTimer: null,
},
// Bright Switch
bright: {
button: document.getElementById('gif_tv_button_bright'),
isActive: true,
message: document.getElementById('gif_tv_message_bright'),
messageTimer: null,
},
// Black & White Switch
color: {
button: document.getElementById('gif_tv_button_color'),
isActive: true,
message: document.getElementById('gif_tv_message_color'),
messageTimer: null,
},
}
}
log()
{
console.log( 'gif TV on' )
}
playSound( url, volume )
{
const sound = new Audio()
sound.src = url
sound.volume = volume/10
this.switch.mute.isActive !== true ? sound.play() : null
}
displayStatic()
{
this.gifVideo.setAttribute( "src", this.staticGIF )
}
displayChannel()
{
this.gifVideo.setAttribute( "src", this.currentChannelURL )
}
updateMessage( messageObj, theMessage )
{
let messageElem = messageObj.message
messageElem.innerHTML = theMessage
clearTimeout(messageObj.messageTimer);
if ( !messageElem.classList.contains( 'active' ) ) {
messageElem.classList.add( 'active' )
}
messageObj.messageTimer = setTimeout(() => {
messageElem.classList.remove( 'active' )
}, 2000);
}
leadingZero(num, size)
{
var int = num+"";
while (int.length < size) int = "0" + int;
return int;
}
changeChannel( direction )
{
const updateMessage = () => {
let channelsDecimal = (this.channels.length+1)/10
let channelIndex = this.leadingZero(this.dial.channel.currentIndex+1, channelsDecimal < 2 ? 2 : channelsDecimal )
this.updateMessage( this.dial.channel, `CH ${channelIndex}` )
}
this.playSound( this.sound.dial, this.dial.volume.currentIndex )
this.playSound( this.sound.static, this.dial.volume.currentIndex/50 )
this.displayStatic()
switch ( direction ) {
case 'up':
this.dial.channel.currentIndex === this.channels.length-1 ? this.dial.channel.currentIndex = 0 : this.dial.channel.currentIndex++
updateMessage()
break;
case 'down':
this.dial.channel.currentIndex === 0 ? this.dial.channel.currentIndex = this.channels.length-1 : this.dial.channel.currentIndex--
updateMessage()
break;
default:
this.dial.channel.currentIndex === this.channels.length-1 ? this.dial.channel.currentIndex = 0 : this.dial.channel.currentIndex++
updateMessage()
}
setTimeout(() => {
this.currentChannelURL = this.channels[this.dial.channel.currentIndex]
this.displayChannel()
}, 333);
}
changeVolume( direction )
{
const updateMessage = () => {
let volumeIndex = this.leadingZero(this.dial.volume.currentIndex, 2 )
this.updateMessage( this.dial.volume, `VOL ${volumeIndex}` )
}
this.playSound( this.sound.dial, this.dial.volume.currentIndex )
switch ( direction ) {
case 'up':
this.dial.volume.currentIndex === 10 ? null : this.dial.volume.currentIndex++
updateMessage()
break;
case 'down':
this.dial.volume.currentIndex === 1 ? null : this.dial.volume.currentIndex--
updateMessage()
break;
default:
this.dial.volume.currentIndex === 10 ? null : this.dial.volume.currentIndex++
updateMessage()
}
}
toggleMute()
{
if ( this.switch.mute.isActive )
{
this.switch.mute.isActive = false
this.playSound( this.sound.switch, this.dial.volume.currentIndex )
this.updateMessage( this.switch.mute, `SOUND` )
}
else
{
this.playSound( this.sound.switch, this.dial.volume.currentIndex )
this.switch.mute.isActive = true
this.updateMessage( this.switch.mute, `MUTE` )
}
}
toggleHighDef()
{
this.playSound( this.sound.switch, this.dial.volume.currentIndex );
if ( this.switch.hd.isActive )
{
this.pixels.style.setProperty( 'visibility', 'hidden' );
this.switch.hd.isActive = false
this.updateMessage( this.switch.hd, `HIGH DEF` )
}
else
{
this.pixels.style.setProperty( 'visibility', 'visible' );
this.switch.hd.isActive = true
this.updateMessage( this.switch.hd, `STND DEF` )
}
}
toggleHueShift()
{
this.playSound( this.sound.switch, this.dial.volume.currentIndex );
if ( this.switch.hue.isActive )
{
this.gifVideo.setAttribute( "style", 'filter: none' );
this.switch.hue.isActive = false
// since this is overriding colorOn we set it back to the default
this.switch.color.isActive = true
this.updateMessage( this.switch.hue, `NO SHIFT` )
}
else
{
this.gifVideo.setAttribute( "style", 'animation: rainbow_barf infinite 2000ms;' );
this.switch.hue.isActive = true
// since this is overriding colorOn we set it back to the default
this.switch.color.isActive = true
this.updateMessage( this.switch.hue, `HUE SHIFT` )
}
}
toggleBright()
{
this.playSound( this.sound.switch, this.dial.volume.currentIndex );
if ( this.switch.bright.isActive )
{
this.viewport.setAttribute( "style", 'opacity: 0.5;' );
this.switch.bright.isActive = false
this.updateMessage( this.switch.bright, `DARK` )
}
else
{
this.viewport.setAttribute( "style", 'opacity: 1;' );
this.switch.bright.isActive = true
this.updateMessage( this.switch.bright, `BRIGHT` )
}
}
toggleColor()
{
this.playSound( this.sound.switch, this.dial.volume.currentIndex );
if ( this.switch.color.isActive )
{
this.gifVideo.setAttribute( "style", 'filter: grayscale(100%);' );
this.switch.color.isActive = false
// since this is overriding invertColor we set it back to the default
this.switch.hue.isActive = false
this.updateMessage( this.switch.color, `B&W` )
}
else
{
this.gifVideo.setAttribute( "style", 'filter: none' );
this.switch.color.isActive = true
// since this is overriding invertColor we set it back to the default
this.switch.hue.isActive = false
this.updateMessage( this.switch.color, `COLOR` )
}
}
init()
{
// this.log();
this.displayChannel();
// Channel Dial
this.dial.channel.button.addEventListener( 'click', () => { this.changeChannel( 'up' ) })
this.dial.channel.button.addEventListener( 'contextmenu', (e) => { e.preventDefault(); this.changeChannel( 'down' ); })
// Volume Dial
this.dial.volume.button.addEventListener( 'contextmenu', (e) => { e.preventDefault(); this.changeVolume( 'up' ); })
this.dial.volume.button.addEventListener( 'click', () => { this.changeVolume( 'down' ) })
// Mute Switch
this.switch.mute.button.addEventListener( 'click', () => { this.toggleMute() })
// HD Switch
this.switch.hd.button.addEventListener( 'click', () => { this.toggleHighDef() })
// Hue Shift Switch
this.switch.hue.button.addEventListener( 'click', () => { this.toggleHueShift() })
// Brightness Switch
this.switch.bright.button.addEventListener( 'click', () => { this.toggleBright() })
// Black and White Switch
this.switch.color.button.addEventListener( 'click', () => { this.toggleColor() })
document.onkeydown = ( e ) =>
{
e.preventDefault();
// left key
e.keyCode == '39' ? this.changeChannel( 'up' ) : null
// right key
e.keyCode == '37' ? this.changeChannel( 'down' ) : null
// up key
e.keyCode == '38' ? this.changeVolume( 'up' ) : null
// down key
e.keyCode == '40' ? this.changeVolume( 'down' ) : null
}
}
}
const gifTV = new GifTV( gifTVURLs )
gifTV.init()
@import url('https://fonts.googleapis.com/css?family=Roboto|VT323&display=swap');
html {
@media ( max-width: 480px ) {
font-size: 12px;
}
@media ( min-width: 480px ) and ( max-width: 768px ) {
font-size: 16px;
}
@media ( min-width: 768px ) {
font-size: 20px;
}
}
body {
position: relative;
background-color: #000;
font-size: 1rem;
font-family: Roboto;
color: #ccc;
&:before {
position: fixed;
z-index: -999;
top: 0; right: 0; bottom: 0; left: 0;
background-image: url('https://res.cloudinary.com/cyborgspaceviking/image/upload/v1571119521/space-background_fowfq3.jpg');
opacity: 0.3;
content: '';
}
}
img {
display: block;
width: 100%;
height: auto;
}
.row {
padding-top: 3rem;
padding-bottom: 3rem;
}
.gutter {
padding-right: 2rem;
padding-left: 2rem;
}
.wrapper {
max-width: 1080px;
margin: auto;
}
.gif-tv {
position: relative;
margin-right: 1rem;
.viewport {
position: absolute;
top: 9%;
right: 26%;
bottom: 15%;
left: 7%;
background: #161616;
z-index: -1;
overflow: hidden;
.video {
z-index: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.pixels {
z-index: 1;
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
content: '';
}
.meta-left,
.meta-right {
display: flex;
flex-flow: column;
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
width: 50%;
padding: 4% 6%;
}
.meta-left {
align-items: flex-start;
left: 0;
right: unset;
}
.meta-right {
align-items: flex-end;
left: unset;
right: 0;
}
span {
text-shadow: 0 0 3px #888;
font-size: 4rem;
font-family: VT323;
}
span:not(.active) {
display: none;
}
span.active {
display: block;
}
}
button.dial {
display: block;
position: absolute;
left: 85.6%;
transform: translateY(-50%);
padding: 0;
border-radius: 50%;
border: #DFDDD1 solid 3px;
background-color: transparent;
width: 10.2%;
line-height: 0;
cursor: pointer;
transition: all 200ms ease-in-out;
&:hover,
&:focus {
outline: none;
}
&:hover {
border-color: #00aaff;
}
&:active {
border-color: #007fff;
}
&::before {
display: block;
width: 100%;
padding-top: 100%;
content: '';
}
}
button#gif_tv_button_channel { top: 56.3%; }
button#gif_tv_button_volume { top: 74.6%; }
button.switch {
display: block;
position: absolute;
left: 82.2%;
transform: translateY(-50%);
padding: 0;
border-radius: 50%;
border: none;
background-color: #DFDDD1;
width: 1.6%;
line-height: 0;
cursor: pointer;
transition: all 200ms ease-in-out;
&:hover,
&:focus {
outline: none;
border: none;
}
&:hover {
background-color: #00aaff;
}
&:active {
background-color: #007fff;
}
&::before {
display: block;
width: 100%;
padding-top: 100%;
content: '';
}
}
button#gif_tv_button_mute { top: 53.7%; }
button#gif_tv_button_hd { top: 60.2%; }
button#gif_tv_button_hue_shift { top: 66.5%; }
button#gif_tv_button_bright { top: 72.9%; }
button#gif_tv_button_color { top: 79.1%; }
@keyframes rainbow_barf {
0% {
filter: hue-rotate(0deg);
}
25% {
filter: hue-rotate(90deg);
}
50% {
filter: hue-rotate(180deg);
}
75% {
filter: hue-rotate(270deg);
}
100% {
filter: hue-rotate(360deg);
}
}
.cta {
position: absolute;
top: 0;
right: 0;
transform-origin: bottom left;
transform: translate( 100%, 0) rotate(90deg);
text-shadow: 0 2px 8px #999;
text-transform: uppercase;
letter-spacing: 1px;
}
}
.heading {
width: 100%;
margin: 10% 0;
text-shadow: 0 2px 8px #999;
letter-spacing: 0.2em;
font-weight: bold;
font-size: 4rem;
text-align: center;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment