<!-- part of article: https://utilitybend.com/blog/revisiting-svg-filters-my-forgotten-powerhouse-for-duotones-noise-and-other-effects -->

<svg width="0" height="0" viewBox="0 0 500 350" aria-hidden="true">
  <filter id="blurX">
    <feGaussianBlur stdDeviation="0 0"></feGaussianBlur>
  </filter>
  <filter id="blurXSoft">
    <feGaussianBlur stdDeviation="0 0"></feGaussianBlur>
  </filter>
  <filter id="blurXHard">
    <feGaussianBlur stdDeviation="0 0"></feGaussianBlur>
  </filter>
  <filter id="blurBoth">
    <feGaussianBlur stdDeviation="0"></feGaussianBlur>
  </filter>

  <filter id='noiseFilter'>
    <feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch' />
  </filter>
</svg>

<div class="wrapper">
  <section class="background"></section>
  <section class="road"></section>
  <div class="car">
    <div class="wheel"></div>
    <div class="wheel"></div>
  </div>
  <div class="blur-control">
    <div>
      <input type="radio" name="blur" id="blur" class="motion" checked />
      <label for="blur">SVG blur (x)</label></label>
    </div>
    <div>
      <input type="radio" name="blur" id="gausianBlur" class="gausian" />
      <label for="gausianBlur">CSS blur</label></label>
    </div>
    <div>
      <input type="radio" name="blur" id="noblur" class="noblur" />
      <label for="noblur">no blur</label></label>
    </div>
  </div>
  <div class="speed">
    <label for="slider">Speed</label>
    <input type="range" id="slider" min="0" max="10" step="0.1" value="5">
  </div>

</div>
@layer layout, backgrounds, animations, controls;

@import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");

:root {
  --slider-value: 0;
  --base-landscape-duration: 126s;
  --base-road-duration: 8s;
  --base-wheel-duration: 3.7s;
}

@layer backgrounds {
  .background {
    height: 100dvh;
    width: 100%;
    background-image: url("https://assets.codepen.io/159218/mountain-landscape.svg");
    background-repeat: repeat-x;
    background-position-y: bottom;
    background-size: 250vw auto;
    animation: landscape
      calc(var(--base-landscape-duration) / (var(--slider-value) * 0.5 + 1))
      linear infinite;
  }

  .road {
    position: absolute;
    inset: 0;
    background-image: url("https://assets.codepen.io/159218/road-pp.svg");
    background-repeat: repeat-x;
    background-size: 126vw auto;
    background-position-y: bottom;
    height: 100vh;
    animation: road
      calc(var(--base-road-duration) / (var(--slider-value) * 0.5 + 1)) linear
      infinite;
  }

  .car {
    position: relative;
    width: 25vw;
    aspect-ratio: 1.25;
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translate(-20%, -15%);
    background-image: url("https://assets.codepen.io/159218/car-top.svg");
    background-repeat: no-repeat;
  }

  .wheel {
    position: absolute;
    bottom: 0;
    width: 22%;
    aspect-ratio: 1;
    background-image: url("https://assets.codepen.io/159218/car-wheel.svg");
    background-repeat: no-repeat;
    bottom: 39%;
    left: 13%;
    animation: rotate
      calc(var(--base-wheel-duration) / (var(--slider-value) * 0.5 + 1)) linear
      infinite;
    &:nth-child(2) {
      left: auto;
      right: 12.5%;
    }
  }
}

@layer controls {
  :root:has(.motion:checked) {
    .background {
      filter: url(#blurX);
    }
    .road {
      filter: url(#blurXHard);
    }
    .wheel {
      filter: url(#blurBoth);
    }
    .car {
      filter: url(#blurXSoft);
    }
  }

  :root:has(.gausian:checked) {
    .background {
      filter: blur(calc((var(--slider-value) / 10) * 10px));
    }
    .road {
      filter: blur(calc((var(--slider-value) / 10) * 15px));
    }
  }
}

@layer animations {
  @keyframes road {
    from {
      background-position-x: 0%;
    }
    to {
      background-position-x: -444%;
    }
  }

  @keyframes landscape {
    from {
      background-position-x: 0%;
    }
    to {
      background-position-x: -500%;
    }
  }

  @keyframes rotate {
    to {
      rotate: -360deg;
    }
  }

  @media (prefers-reduced-motion) {
    * {
      animation: none !important;
    }
  }
}

@layer layout {
  body {
    margin: 0;
    background: skyblue;
    background: linear-gradient(
      180deg in oklab,
      oklch(55% 0.45 245) -2% -2%,
      oklch(95% 0.4 74) 92% 92%
    );
    font-family: "Poppins", serif;
    font-weight: 400;
    font-style: normal;
    color: #fff;
  }

  .wrapper {
    &::before {
      position: absolute;
      inset: 0;
      z-index: 2;
      content: "";
      filter: url(#noiseFilter);
      opacity: 0.2;
    }
    &::after {
      position: absolute;
      inset: 0;
      z-index: 1;
      border: 20px solid black;
      content: "";
    }
  }

  .blur-control {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;

    position: fixed;
    top: 50px;
    left: 50%;
    z-index: 3;
    transform: translateX(-50%);
    padding-block: 20px;
    cursor: pointer;
  }

  input[type="radio"] {
    scale: 2;
    width: 32px;
    accent-color: black;
  }

  .speed {
    position: fixed;
    top: 50%;
    right: 0;
    z-index: 3;
  }

  #slider {
    rotate: -90deg;
    accent-color: black;
    scale: 2;
  }

  svg {
    position: absolute;
    width: 0;
    height: 0;
    visibility: hidden;
  }
}
const slider = document.getElementById("slider");
const root = document.documentElement;
const blurXFilter = document.querySelector("#blurX feGaussianBlur");
const blurXHardFilter = document.querySelector("#blurXHard feGaussianBlur");
const blurXSoftFilter = document.querySelector("#blurXSoft feGaussianBlur");
const blurBoth = document.querySelector("#blurBoth feGaussianBlur");

function updateEffects(value) {
  // update the CSS custom property
  root.style.setProperty("--slider-value", value);

  const blurXValue = (value / 10) * 10; // Max value is 5
  const blurXHardValue = (value / 10) * 15; // Max value is 10

  blurXFilter.setAttribute("stdDeviation", `${blurXValue} 0`);
  blurXHardFilter.setAttribute("stdDeviation", `${blurXHardValue} 0`);

  if (value > 9) {
    blurXSoftFilter.setAttribute("stdDeviation", `1 0`);
    blurBoth.setAttribute("stdDeviation", `.2`);
  } else {
    blurXSoftFilter.setAttribute("stdDeviation", `0 0`);
    blurBoth.setAttribute("stdDeviation", `0`);
  }
}

// Set initial values
updateEffects(slider.value);

slider.addEventListener("input", function () {
  const value = parseFloat(this.value);
  updateEffects(value);
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.