Underwater Caustics
Two repeating-conic-gradient layers animated in opposite directions create moiré aquatic caustics — CSS rising bubbles included, zero JavaScript.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →
3 usage examples



How it works
The effect relies on two stacked CSS layers (.caustics-layer), each carrying a pair of repeating-conic-gradient. The first anchors its cones at at 25% 25% and at 75% 75%, tiled at 80 × 80 px and 100 × 100 px. The second shifts them to from 30deg at 60% 40% and from 60deg at 40% 60%, on 90 × 90 px and 70 × 70 px tiles. The different from angle and at position offset the angular phase of both grids, together producing a diamond lattice (4–6 % alpha) that mimics pool caustic patterns.
Both layers share the same causticsMove animation — ±10 px translation, ±2° rotation — but with different durations (4 s and 6 s) and opposite directions (animation-direction: normal vs reverse). This frequency offset (2/3 ratio, 12-s beat) creates an animated moiré: the two tile grids slide against each other continuously, generating the characteristic underwater rippling without JavaScript or canvas.
The background is a linear-gradient on .caustics-container — three stops: #006994 at the surface, #046 at mid-depth, #034 at the bottom — acting as the water column. Both layers are position: absolute; inset: 0 and cover the container at any size. overflow: hidden prevents bleed during translation.
Four bubbles (.caustics-bubbles span) are pure CSS circles — border-radius: 50%, near-transparent fill (rgba(255,255,255,.05)), 1 px solid rgba(255,255,255,.2) border — animated by bubbleRise (ease-in, 220 px rise, scale(1.3)). Four distinct durations (5 s, 6 s, 7 s, 5.5 s) and staggered 0–3 s delays prevent any visible repeat cycle.
Accessibility
- prefers-reduced-motion not implemented: the code contains no
@media (prefers-reduced-motion)rule. ThecausticsMoveandbubbleRiseanimations run even when the user has enabled "Reduce Motion" in their OS. Add before shipping:@media (prefers-reduced-motion: reduce) { .caustics-layer, .caustics-bubbles span { animation: none; } }. - The
.caustics-containercarries noaria-hidden. Since the effect is purely decorative, addaria-hidden="true"to exclude it from the accessibility tree. - No
roleoraria-labelis defined on the container. In real usage, wrap the effect and overlay text in arole="img"with a descriptivearia-label. - No keyboard or pointer interaction — 100% decorative effect. No focus managed, no DOM events. Accessibility depends on overlay content positioned above (higher z-index).
- The
.caustics-label(whitergba(255,255,255,.85)on blue background #006994) achieves an estimated contrast ratio ≥ 4.6:1 — passes WCAG AA for normal text.
Browser compatibility
Requires repeating-conic-gradient (CSS Gradients Level 4). Supported in all modern browsers since 2019–2020. Zero JavaScript, zero external dependencies.
Browsers without <code>repeating-conic-gradient</code> support (IE, old Edge) render only the <code>linear-gradient</code> background — a clean blue gradient, no JS errors, no broken layout.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="caustics-container">
<div class="caustics-layer"></div>
<div class="caustics-layer"></div>
<div class="caustics-bubbles">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| linear-gradient on .caustics-container | #006994, #046, #034 | Water background colors — change the 3 stops to adjust perceived depth (bright aqua, midnight blue, seaweed green). |
| animation-duration: 4s (.caustics-layer) | 4s | Layer 1 speed. 4 s = dynamic, 8 s = slow and contemplative. Keep a non-integer ratio with layer 2 for a richer moiré. |
| animation-duration: 6s (.caustics-layer:nth-child(2)) | 6s | Layer 2 speed (reverse direction). The 4s/6s ratio creates a 12-s beat — change it to lengthen or shorten the visual cycle period. |
| rgba(100, 200, 255, .06) in repeating-conic-gradient | .06 / .05 / .04 | Alpha of the bright conic segments. Raise to .15 for very pronounced caustics, lower to .02 for a near-invisible background texture. |
| background-size: 80px 80px (layer 1) | 80px / 100px | Conic tile size — smaller = fine grain, larger = broad light pools. Change both values of the same layer together to keep the composite look. |
| opacity: .8 on .caustics-layer | .8 | Global transparency of each layer. Drop to .4 for subtle, raise to 1 for a saturated render. |
| animation-duration on .caustics-bubbles span | 6s, 5s, 7s, 5.5s | Rise duration per bubble. Mixed values = natural async rise. Identical values = regular, hypnotic rhythm. |
| translateY(-220px) in @keyframes bubbleRise | -220px | Vertical travel distance of bubbles. Match to the actual container height so bubbles exit precisely at the top. |
FAQ
repeating-conic-gradient is supported since Chrome 69 (2018), Firefox 83 (2020), and Safari 12.1 (2019). On Internet Explorer or unsupported browsers, only the linear-gradient background renders — a clean blue gradient, no errors, no broken layout.position: absolute; z-index: 2 inside the same container (position: relative; overflow: hidden). Add a scrim if needed — an inset: 0 absolute div at z-index: 1 with background: linear-gradient(rgba(0,0,0,.4), rgba(0,0,0,.3)) — to ensure WCAG AA text contrast.