<!-- 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);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.