Bokeh particles with no filter

Three hundred points of light sit at different depths, and as the focal plane moves, each one out of focus becomes a growing soft disc, as a lens draws it. focus() with max: 1 returns the defocus, 0 on the plane and close to 1 far from it, and seek() writes each light's style from it through one static pool.

  • The disc is a radial gradient whose hard edge moves inward and whose size grows with the blur, so the softness comes from the gradient.
  • The pool writes 300 styles per seek, and none sets a filter.

The same pattern fits dust, stars, rain and city lights. For a filter that blurs a few bands at once, see Depth bands for hundreds of elements. The stylesheet needs .light { position: absolute; left: 0; top: 0; border-radius: 50%; }.

<div id="stage" data-composition-id="bokeh"     data-start="0" data-duration="5" data-width="1280" data-height="720" data-fps="30">
    <div class="layer" data-component="bokeh" data-start="0" data-duration="5">
        <div data-pool="lights" data-key="id"><template><i class="light" data-bind-style="style"></i></template></div>
    </div>
</div>
const { scene, random, focus, styleAt } = wildflower.kinetic;

const COUNT = 300;

scene('bokeh', {
    pools: { lights: {} },
    keyframes: {
        plane: { at: [0.5, 4.5], to: [-1000, 0], ease: 'power2.inOut' },
    },
    init() {
        const rnd = random(23);
        for (let i = 0; i < COUNT; i++) {
            this.pools.lights.add({ id: i, x: rnd() * 1280, y: rnd() * 720, z: -rnd() * 1000, base: 3 + rnd() * 3, hue: 30 + rnd() * 40, style: {} });
        }
    },
    seek(local) {
        const plane = this.plane;
        this.pools.lights.forEach((l) => {
            const blur = focus(l.z, plane, { range: 250, max: 1 });    // 0 sharp, 1 fully out of focus
            const size = l.base * (1 + 6 * blur);                       // the disc grows with the blur
            const edge = 100 - 70 * blur;                               // and its edge softens
            this.pools.lights.update(l.id, {
                style: {
                    width: size.toFixed(1) + 'px',
                    height: size.toFixed(1) + 'px',
                    ...styleAt(local, { translateX: l.x - size / 2, translateY: l.y - size / 2 }),
                    opacity: (1 - 0.6 * blur).toFixed(3),
                    background: 'radial-gradient(circle, hsl(' + l.hue.toFixed(0) + ' 90% 70%) ' + (edge - 30).toFixed(0) + '%, transparent ' + edge.toFixed(0) + '%)',
                },
            });
        });
    },
});
html, body { margin: 0; background: #0a0b10; }

#stage { position: relative; overflow: hidden; background: #0b0a14; }

.layer { position: absolute; inset: 0; }

.light { position: absolute; left: 0; top: 0; border-radius: 50%; }

[data-composition-id]:not([data-kinetic-ready]) { visibility: hidden; }
Bokeh particles with no filter

The Showreel demo's focus scene drifts its bokeh lights behind the phrases this way.