GIF effect

using plain image, video element and mix-blend-mode

by Konstantin Rouda

HTML

<!-- <section class="c-container">
    <img src="https://farm1.staticflickr.com/798/40381373534_ae61b918db_o.jpg" width="1920" height="1280" alt="girl in winter jacket_1920">
    <video class="video-elem" id='myVideo' autoplay loop muted>
        <source src="//konrud.com/external/video/snow_particles_22s_1080p.mp4" type="video/mp4" >
    </video>
</section>   -->
 
<main class="c-main">

    <!-- Snow/Rain  -->
    <section id="main-container" class="c-container">
     <header class="c-container__header"><h3 class="c-container__title">Snow Effect</h3></header>
      <article class="c-container__content">
        <video class="c-video-elem has-snow-effect" id='snow-video-layer' loop muted playsinline>
            <source src="//konrud.com/external/video/snowflakes_falling_1080p.mp4" type="video/mp4" >
       </video>
       <video class="c-video-elem has-rain-effect" id='rain-video-layer' loop muted playsinline>
            <source src="//konrud.com/external/video/lightning_n_rain_11s_1080p.mp4" type="video/mp4" >
       </video>
        <img src="https://farm1.staticflickr.com/798/40381373534_ae61b918db_o.jpg" class="o-image-layer c-container__img" width="1920" height="1280" alt="girl in winter jacket_1920">
      </article>
      <footer class="c-container__footer">
        <div id="effects-controllers-container" class="c-container__controls c-controls">
          
          <label for="snow-controller" id="snow-controller-wrapper" class="c-controller-label c-controls__snow-controller-wrapper">
            <input type="radio" class="c-controller o-radio js-snowController c-controls__snow-controller" id="snow-controller" name="controller" value="snow" />
            <span class="c-controller-text">Let it snow</span>
          </label>  
          
          <label for="rain-controller" id="rain-controller-wrapper" class="c-controller-label c-controls__rain-controller-wrapper">
            <input type="radio" class="c-controller o-radio js-rainController...

CSS

HTML {
  /*using system font-stack*/
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  font-size: 115%; /*~18px*/
  font-size: calc(16px + (30 - 16) * (100vw - 300px) / (1300 - 300) );
  line-height: 1.5;
  box-sizing: border-box;
}

BODY {
  margin: 0;
  color: #3a3d40;
  background-color: rgb(252, 252, 252);
}

*, *::before, *::after {
  box-sizing: inherit;
  color: inherit;
}

IMG {
    max-width: 100%;
    height: 100%;
}

/*Actual Style*/
.c-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 100vh;
  padding: 1rem;
/*   background: red; */
}

.o-image-layer {
  vertical-align: bottom;
}

.c-container__img {
}

/*
.o-radio {
  -webkit-appearance: none;
  border: 1px solid cornflowerblue;
  width: 0.8rem; /!*~13px*!/
  height: 0.8rem; /!*~13px*!/
  border-radius: 50%;
  vertical-align: -0.0625rem; /!*~-1px*!/
  background: transparent;
  box-shadow: 33px 0px 0px 6px rgb(1, 1, 1) inset;
  transition: box-shadow 0.3s;
}

.o-radio:active {
  box-shadow: -33px 0px 0px 6px rgb(1, 1, 1) inset;
  transition-duration: 0s;
}

.o-radio:checked {
 box-shadow: 0px 0px 0px 5px rgb(1, 1, 1) inset, 0px 0px 0px 15px rgb(18, 214, 255) inset;
  box-shadow: 0px 0px 0px 5px rgb(1, 1, 1) inset, 0px 0px 0px 15px rgb(18, 214, 255) inset;
}
*/

.o-radio {
  -webkit-appearance: none;
  border: 1px solid cornflowerblue;
  width: 0.8rem; /*~13px*/
  height: 0.8rem; /*~13px*/
  border-radius: 50%;
  vertical-align: -0.0625rem; /*~-1px*/
  background: transparent;
/*   box-shadow: 33px 0px 0px 6px rgb(1, 1, 1) inset; */
  box-shadow: 2.06250rem 0rem 0rem 0.37500rem rgb(1, 1, 1) inset; /*~33px ~0px ~0px ~6px rgb(1, 1, 1) inset*/
  transition: box-shadow 0.3s;
}

.o-radio:active {
  box-shadow: -2.06250rem/*-33px*/ 0px 0px 0.375rem/*~6px*/ rgb(1, 1, 1) inset;
  transition-duration: 0s;
}

.o-radio:checked {
/* box-shadow: 0px 0px 0px 5px rgb(1, 1, 1) inset, 0px 0px 0px 15px...

JavaScript

;(function () {
  "use strict";
  
  const SNOW_CONTROLLER_JS_CLASS = "js-snowController";
  const HIDDEN_CLASS = "is-hidden";
  
  const mainContainer = document.getElementById("main-container");
  const snowVideo = document.getElementById("snow-video-layer");
  const rainVideo = document.getElementById("rain-video-layer");
  const effectsControllersContainer = document.getElementById("effects-controllers-container");
  
  const snowControllerWrapper = document.getElementById("snow-controller-wrapper");
  const rainControllerWrapper = document.getElementById("rain-controller-wrapper");
  
  snowVideo.addEventListener("canplay", snowVideoLayerOnCanPlay);
  rainVideo.addEventListener("canplay", rainVideoLayerOnCanPlay);
  
  // video.classList.add(HIDDEN_CLASS);
  // snowVideo.style.opacity = 0;
  // rainVideo.style.opacity = 0;
  
  document.addEventListener("DOMContentLoaded", onDOMContentLoaded);
  
  effectsControllersContainer.addEventListener("change", effectsControllersOnChecked, true);
  
  
  function onDOMContentLoaded (e) {
     _hideAllVideos(mainContainer);
  };
  
  function snowVideoLayerOnCanPlay (e) {
     // video.style.opacity = 1;
     snowControllerWrapper.classList.remove(HIDDEN_CLASS);
  };
  
  function rainVideoLayerOnCanPlay (e) {
    rainControllerWrapper.classList.remove(HIDDEN_CLASS);
  };
  
  
  function effectsControllersOnChecked (e) {
    const controller = e.target;
    debugger;
    if(controller.type !== "radio" || controller.checked === false) return;
     
    const controllerValue = controller.value;
    
    const container = controller.closest(".c-container");
     
    if(container == null) return;
   
    let videoElem;
      
    // let classListMethod = "add";
     
    switch(controllerValue) {
      case "snow":
        videoElem = container.querySelector("#snow-video-layer");
        break;
      case "rain":
        videoElem = container.querySelector("#rain-video-layer");
        break;
      default:
       ...