<div id="screen"></div>
body {
  margin: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background-color: #000;
}


canvas {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 9998;
  // background-color: #fff;
  width: 100%;
  height: 100%;
  
  &.snow {
    background-color: #aaa;
    opacity: 0.2;
  }
}

#screen {
  width: 100%;
  height: 100%;
  background: transparent
    linear-gradient(to bottom, #85908c 0%, #323431 100%)
    repeat
    scroll
    0
    0;
  // background-image: url(https://images.unsplash.com/photo-1517672651691-24622a91b550?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ);
  background-size: cover;
}


$screen-background: #121010;


@mixin pseudo {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  content: " ";
}

@mixin fill {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0; 
}

.screen-container {
    width: 640px;
    height: 360px;
  overflow: hidden;
  position: relative;
}

.screen-wrapper {
  position: relative;
  width: 100%;
  height: 100%; 
  
  &:first-child {
    // opacity: 0;
  }
}

.vcr {
  // filter: blur(1px);
  opacity: 0.6
}
.video {
  filter: blur(1px);
  width: 100%;
  height: 100%; 
}
.image {
  width: 100%;
  height: auto;
  filter: blur(1.2px);
}
.vignette {
  background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/86186/crt.png);
  @include fill;
    background-repeat: no-repeat;
    background-size: 100% 100%; 
  z-index: 10000;
}
.scanlines {
  @include fill;
  z-index: 9999;
  background: linear-gradient(
      transparentize($screen-background, 1) 50%,
      transparentize(darken($screen-background, 10), 0.75) 50%
    ),
    linear-gradient(
      90deg,
      transparentize(#ff0000, 0.94),
      transparentize(#00ff00, 0.98),
      transparentize(#0000ff, 0.94)
    );
  background-size: 100% 2px, 3px 100%;
  pointer-events: none;
}

.wobblex {
  animation: wobblex 100ms infinite;
}

.wobbley {
  animation: wobbley 100ms infinite;
}

.glitch {
  animation: 5s ease 2000ms normal none infinite running glitch;
}

@keyframes wobblex {
  50% {
    transform: translateX(1px);
  }
  51% {
    transform: translateX(0);
  }
}
@keyframes wobbley {
  0% {
    transform: translateY(1px);
  }
  100% {
    transform: translateY(0);
  }
}
@keyframes glitch {
  30% {
  }
  40% {
    opacity: 1;
    transform: scale(1, 1);
    transform: skew(0, 0);
  }
  41% {
    opacity: 0.8;
    transform: scale(1, 1.2);
    transform: skew(80deg, 0);
  }
  42% {
    opacity: 0.8;
    transform: scale(1, 1.2);
    transform: skew(-50deg, 0);
  }
  43% {
    opacity: 1;
    transform: scale(1, 1);
    transform: skew(0, 0);
  }
  65% {
  }
}

@keyframes glitch1 {
  0% {
    transform: translateX(0);
  }
  30% {
    transform: translateX(0);
  }
  31% {
    transform: translateX(10px);
  }
  32% {
    transform: translateX(0);
  }
  98% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(10px);
  }
}
.text span:nth-child(2) {
  animation: glitch2 1s infinite;
}
@keyframes glitch2 {
  0% {
    transform: translateX(0);
  }
  30% {
    transform: translateX(0);
  }
  31% {
    transform: translateX(-10px);
  }
  32% {
    transform: translateX(0);
  }
  98% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-10px);
  }
}

.overlay .text {
  animation: 5s ease 2000ms normal none infinite running glitch;
}

.on > .screen-wrapper {
  animation: 3000ms linear 0ms normal forwards 1 running on;
}
.off > .screen-wrapper {
  animation: 750ms
    cubic-bezier(0.230, 1.000, 0.320, 1.000)
    0ms
    normal
    forwards
    1
    running
    off;
}

@keyframes on {
  0% {
    transform: scale(1, 0.8) translate3d(0, 0, 0);
    filter: brightness(4);
    opacity: 1;
  }
  3.5% {
    transform: scale(1, 0.8) translate3d(0, 100%, 0);
  }

  3.6% {
    transform: scale(1, 0.8) translate3d(0, -100%, 0);
    opacity: 1;
  }

  9% {
    transform: scale(1.3, 0.6) translate3d(0, 100%, 0);
    filter: brightness(4);
    opacity: 0;
  }

  11% {
    transform: scale(1, 1) translate3d(0, 0, 0);
    filter: contrast(0) brightness(0);
    opacity: 0;
  }

  100% {
    transform: scale(1, 1) translate3d(0, 0, 0);
    filter: contrast(1) brightness(1.2) saturate(1.3);
    opacity: 1;
  }
}

@keyframes off {
  0% {
    transform: scale(1, 1);
    filter: brightness(1);
  }
  40% {
    transform: scale(1, 0.005);
    filter: brightness(100);
  }
  70% {
    transform: scale(1, 0.005);
  }
  90% {
    transform: scale(0.005, 0.005);
  }
  100% {
    transform: scale(0, 0);
  }
}

.roller {
  position: relative;
  
  // &::after {
  //  width: 100%;
  //  height: 3px;
  //  position: absolute;
  //  left: 0;
  //  top: 0;
  //  background-color: rgba(0,0,0,0.2);
  //  filter: blur(1px);
  //  content: "";
  //  animation: 1000ms linear 0ms forwards infinite roll;
  // }
  animation: 2000ms linear 0ms forwards infinite roll;
}

@keyframes roll {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(0, -50%, 0);
  } 
}

.dg.ac {
  z-index: 10000 !important;
}
View Compiled
function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

class ScreenEffect {
  constructor(parent, options) {
    this.parent = parent;
    if ( typeof parent === "string" ) {
      this.parent = document.querySelector(parent);
    }
    
    this.config = Object.assign({}, {
      //
    }, options)
    
    this.effects = {};
    
    this.events = {
      resize: this.onResize.bind(this)
    };
    
    window.addEventListener("resize", this.events.resize, false);
    
    this.render();
  }
  
  render() {
    
    const container = document.createElement("div");
    container.classList.add("screen-container");
    
    const wrapper1 = document.createElement("div");
    wrapper1.classList.add("screen-wrapper");
    
    const wrapper2 = document.createElement("div");
    wrapper2.classList.add("screen-wrapper"); 
    
    const wrapper3 = document.createElement("div");
    wrapper3.classList.add("screen-wrapper");     
    
    wrapper1.appendChild(wrapper2);
    wrapper2.appendChild(wrapper3);
    
    container.appendChild(wrapper1);
    
    this.parent.parentNode.insertBefore(container, this.parent);
    wrapper3.appendChild(this.parent);
    
    this.nodes = { container, wrapper1, wrapper2, wrapper3 };
    
    this.onResize();
  }
  
  onResize(e) {
    this.rect = this.parent.getBoundingClientRect();
    
    if ( this.effects.vcr && !!this.effects.vcr.enabled ) {
      this.generateVCRNoise();
    }
  }
  
  add(type, options) {
    
    const config = Object.assign({}, {
      fps: 30,
      blur: 1
    }, options);
    
    if ( Array.isArray(type) ) {
      for ( const t of type ) {
        this.add(t);
      }
      
      return this;
    }
    
    const that = this;
    
    if ( type === "snow" ) {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      canvas.classList.add(type);
      canvas.width = this.rect.width / 2;
      canvas.height = this.rect.height / 2;
      
      this.nodes.wrapper2.appendChild(canvas);
      
      animate();
      // that.generateSnow(ctx);
      
      function animate() {
        that.generateSnow(ctx);
        that.snowframe = requestAnimationFrame(animate);
      }
      
      this.effects[type] = {
        wrapper: this.nodes.wrapper2,
        node: canvas,
        enabled: true,
        config
      };
      
      return this;
    }
    
    if ( type === "roll" ) {
      return this.enableRoll();
    }
    
    if ( type === "vcr" ) {
      const canvas = document.createElement("canvas");
      canvas.classList.add(type);
      this.nodes.wrapper2.appendChild(canvas);
      
      canvas.width = this.rect.width;
      canvas.height = this.rect.height;
      
      this.effects[type] = {
        wrapper: this.nodes.wrapper2,
        node: canvas,
        ctx: canvas.getContext("2d"),
        enabled: true,
        config
      };      
      
      this.generateVCRNoise();
      
      return this;
    }
    
    let node = false;
    let wrapper = this.nodes.wrapper2;
    
    switch(type) {
      case "wobblex":
      case "wobbley":
        wrapper.classList.add(type);
        break;
      case "scanlines":
        node = document.createElement("div");
        node.classList.add(type);
        wrapper.appendChild(node);
        break;
      case "vignette":
        wrapper = this.nodes.container;
        node = document.createElement("div");
        node.classList.add(type);
        wrapper.appendChild(node);
        break;
      case "image":
        wrapper = this.parent;
        node = document.createElement('img');
        node.classList.add(type);

        node.src = config.src;

        wrapper.appendChild(node);
        break;        
      case "video":
        wrapper = this.parent;
        node = document.createElement('video');
        node.classList.add(type);

        node.src = config.src;
        node.crossOrigin = 'anonymous';
        node.autoplay = true;
        node.muted = true;
        node.loop = true;
        wrapper.appendChild(node);
        break;
    }

    this.effects[type] = {
      wrapper,
      node,
      enabled: true,
      config
    };
    
    return this;
  }
  
  remove(type) {

    const obj = this.effects[type];
    if ( type in this.effects && !!obj.enabled ) {
      obj.enabled = false;
      
      if ( type === "roll" && obj.original ) {
        this.parent.appendChild(obj.original);
      }     
      
      if ( type === "vcr" ) {
        clearInterval(this.vcrInterval);
      }
      
      if ( type === "snow" ) {
        cancelAnimationFrame(this.snowframe);
      }     
      
      if ( obj.node ) {
        obj.wrapper.removeChild(obj.node);
      } else {
        obj.wrapper.classList.remove(type);
      }
    }
    
    return this;
  }
  
  enableRoll() {
    const el = this.parent.firstElementChild;
    
    if ( el ) {
      const div = document.createElement("div");
      div.classList.add("roller");
      
      this.parent.appendChild(div);
      div.appendChild(el);
      div.appendChild(el.cloneNode(true));
      
      // if ( this.effects.vcr.enabled ) {
      //  div.appendChild(this.effects.vcr.node);
      // }
      
      this.effects.roll = {
        enabled: true,
        wrapper: this.parent,
        node: div,
        original: el
      };
    }
  }
  
  generateVCRNoise() {
    
    
    const canvas = this.effects.vcr.node;
    const config = this.effects.vcr.config;
    const div = this.effects.vcr.node;
    
    if ( config.fps >= 60 ) {
      cancelAnimationFrame(this.vcrInterval);
      const animate = () => {
        this.renderTrackingNoise();
        this.vcrInterval = requestAnimationFrame(animate);
      };
      
      animate();
    } else {
      clearInterval(this.vcrInterval);
      this.vcrInterval = setInterval(() => {
        this.renderTrackingNoise();
      }, 1000 / config.fps);
    }
  }
  
  // Generate CRT noise
  generateSnow(ctx) {

    var w = ctx.canvas.width,
      h = ctx.canvas.height,
      d = ctx.createImageData(w, h),
      b = new Uint32Array(d.data.buffer),
      len = b.length;

    for (var i = 0; i < len; i++) {
      b[i] = ((255 * Math.random()) | 0) << 24;
    }

    ctx.putImageData(d, 0, 0);
  }
  
  renderTrackingNoise(radius = 2, xmax, ymax) {
    
    const canvas = this.effects.vcr.node;
    const ctx = this.effects.vcr.ctx;
    const config = this.effects.vcr.config;
    let posy1 = config.miny || 0;
    let posy2 = config.maxy || canvas.height;
    let posy3 = config.miny2 || 0;
    const num = config.num || 20;
    
    if ( xmax === undefined ) {
      xmax = canvas.width;
    }
    
    if ( ymax === undefined ) {
      ymax = canvas.height;
    }     
    
    canvas.style.filter = `blur(${config.blur}px)`;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = `#fff`;

    ctx.beginPath();
    for (var i = 0; i <= num; i++) {
      var x = Math.random(i) * xmax;
      var y1 = getRandomInt(posy1+=3, posy2);
      var y2 = getRandomInt(0, posy3-=3);
      ctx.fillRect(x, y1, radius, radius);
      ctx.fillRect(x, y2, radius, radius);
      ctx.fill();

      this.renderTail(ctx, x, y1, radius);
      this.renderTail(ctx, x, y2, radius);
    }
    ctx.closePath();
  }

  renderTail(ctx, x, y, radius) {
    const n = getRandomInt(1, 50);

    const dirs = [1, -1];
    let rd = radius;
    const dir = dirs[Math.floor(Math.random() * dirs.length)];
    for (let i = 0; i < n; i++) {
      const step = 0.01;
      let r = getRandomInt((rd -= step), radius);
      let dx = getRandomInt(1, 4);

      radius -= 0.1;

      dx *= dir;

      ctx.fillRect((x += dx), y, r, r);
      ctx.fill();
    }
  } 

}

const screen = new ScreenEffect("#screen", {

});

const gui = new dat.GUI();

const config = {
  effects: {
    roll: {
      enabled: false,
      options: {
        speed: 1000
      }
    },
    image: {
      enabled: true,
      options: {
        src: "https://images.unsplash.com/photo-1505977404378-3a0e28ec6488?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ",        
        blur: 1.2
      }
    },
    vignette: { enabled: true },
    scanlines: { enabled: true },
    vcr: {
      enabled: true,
      options: {
        opacity: 1,
        miny: 220,
        miny2: 220,
        num: 70,
        fps: 60
      }
    },
    wobbley: { enabled: true },
    snow: {
      enabled: true,
      options: {
        opacity: 0.2
      }     
    },
  },
};

const f1 = gui.addFolder("Effects");
const f2 = gui.addFolder("Snow");
const f3 = gui.addFolder("VCR");
const f4 = gui.addFolder("Roll");
const f5 = gui.addFolder("Image");

for ( const effect in config.effects ) {
  const type = config.effects[effect];
  f1.add(type, "enabled").name(effect).onChange(bool => {
    if ( bool ) {
      screen.add(effect, config.effects[effect].options);
    } else {
      screen.remove(effect);
    }
  });
  
  if ( type.options ) {
    let folder = effect === "vcr" || effect === "video" ? f3 : f2;
    for ( const p in type.options ) {
      
      if ( p === "speed" ) {
        f4.add(type.options, p).min(100).step(1).max(10000).onChange(val => {
          screen.effects[effect].node.style.animationDuration = `${val}ms`;
        });
      }
      
      if ( p === "opacity" ) {
        folder.add(type.options, p).name(`${effect} opacity`).min(0).step(0.1).max(1).onChange(val => {
          screen.effects[effect].node.style.opacity = val;
        });
      }
      
      if ( p === "miny" ) {
        folder.add(type.options, p).name(`tracking`).min(0).step(0.1).max(400).onChange(val => {
          screen.effects[effect].config.miny = val;
          screen.effects[effect].config.miny2 = 400 - val;
        });
      }
      
      if ( p === "num" ) {
        folder.add(type.options, p).name(`tape age`).min(1).step(0.1).max(100).onChange(val => {
          screen.effects[effect].config.num = val;
        });
      }
      
      if ( p === "blur" ) {
        f5.add(type.options, p).name(`blur`).min(1).step(0.1).max(5).onChange(val => {
          if ( effect === "vcr" ) {
            screen.effects[effect].config.blur = val;
          } else {
            screen.effects[effect].node.style.filter = `blur(${val}px)`;
          }
        });
      }       
    }
  }
}


f1.open();
f2.open();
f3.open();
f4.open();
f5.open();

setTimeout(() => {
  for ( const prop in config.effects ) {
    if ( !!config.effects[prop].enabled ) {
      screen.add(prop, config.effects[prop].options);
    }
  }
}, 1000);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js