Loading Icon

6 min read

Updated May 23, 2026

A loading icon is the small animated spinner shown inside each thumbnail while the image is being fetched and decoded. FotoGrids ships 38 icon styles and a separate “loaded effect” that controls how the image reveals once it arrives. Together they give every Gallery a polished sense of progress instead of a blank rectangle followed by a sudden image pop.

Location Gallery Editor Settings Effects Loading Effects


Loading Icon #

Loading Icon Style

SettingLoading Icon
Optionsfotogrids bars-rotate-fade spinner 8-dots spinning-circles 12-dots 6-dots-scale 6-dots-rotate ball-triangle gooey-balls bouncing-ball pulse puff dot-revolve 90-ring 180-ring-with-bg 180-ring ring-resize wifi jump 3-dots-rotate 3-dots-bounce 3-dots-fade 3-dots-move 3-dots-scale 4-dots-swing grid blocks-shuffle-5 blocks-shuffle-4 blocks-shuffle-3 blocks-shuffle-2 blocks-scale blocks-wave square square-loader bars-fade bars-scale-fade bars-scale bars
Default12-dots

The picker shows each icon animating in place. Pick the one that fits the Gallery’s character — the fotogrids icon matches the plugin’s own brand mark, spinner and puff are classic neutral choices, the bars and blocks families read as more technical, and the dots and bouncing-ball styles feel softer.

Loading Icon Color

SettingLoading Icon Color
DefaultFotoGrids blue

Sets the colour for every animated part of the icon. The control accepts any colour, including transparency. Leave the field empty to fall back to the FotoGrids brand blue.


Loaded Effect #

The Loaded Effect controls how each thumbnail transitions from the loading state to the revealed image, on a per-item basis. As each image finishes downloading the loading icon is removed and the effect runs only on that thumbnail — heavy Galleries reveal one item at a time rather than waiting for everything before snapping into view.

Loaded Effect

SettingLoaded Effect
OptionsNone Fade Rise Drop Zoom Blur
DefaultFade

The effects describe how the image arrives:

  • None — The image appears instantly the moment it finishes loading. No transition.
  • Fade — The image fades in from transparent to fully opaque.
  • Rise — The image slides up into position as it fades in.
  • Drop — The image slides down into position as it fades in.
  • Zoom — The image scales up from slightly smaller as it fades in.
  • Blur — The image starts blurred and sharpens as it fades in.

While an item is still loading, clicks on it are blocked — the Lightbox will not open until the image has resolved. This prevents an empty Lightbox from being triggered on a thumbnail that has not finished decoding yet.


Tips #

Loading icons appear most visibly on first page load and on slower connections. Open the Gallery in a private browsing window or throttle the connection in browser dev tools to preview how the icon and the Loaded Effect actually behave for visitors.

The same loading icon is also used by the Lightbox spinner and by items loaded dynamically — for example, items appended by pagination or by the Album → Gallery transition. Picking a consistent icon keeps the loading experience uniform across every surface of the Gallery.


Developer Reference #

This section explains how the loading icon system is implemented on the frontend. It exists because the implementation makes some non-obvious choices, and developers extending FotoGrids or building custom themes will want to understand the trade-offs before reaching for the loader markup directly.

Why WAAPI and not SMIL

The loading icons are SVGs. The most natural way to animate an SVG is with SMIL — the <animate> and <animateTransform> elements built into the SVG spec. SMIL is declarative, requires no JavaScript, and is supported in every modern browser.

In practice, however, browsers are not consistent about when SMIL animations actually start. Several real-world conditions cause SMIL animations to be deferred or suppressed until the page has fully loaded or the SVG has been laid out, which produces a frozen, broken-looking loader exactly when the loader matters most — during the initial page load. Cached image responses, ad blockers that mutate the document, server-side rendering frameworks that hydrate after parse, and aggressive resource-priority heuristics in some browsers can all delay SMIL playback.

FotoGrids needs the loader to animate from the first paint, before DOMContentLoaded, and to keep animating until the underlying image has finished decoding. To make that guarantee independent of browser behaviour, the SVG icons are converted to Web Animations API (WAAPI) animations at build time.

How the conversion works

At build time, scripts/loading-icons-build.js reads the source SVGs from src/config/loading-icons.yaml and emits three artefacts:

  • loading-icons.json — SVG strings with all SMIL elements stripped. This is what the frontend renders.
  • loading-icons-smil.json — SVG strings with SMIL intact. The wp-admin settings panel uses these for previews, since previews live on a fully-loaded admin page where SMIL is reliable.
  • loading-icons-waapi.json — A self-contained JavaScript animate(svgEl) function per icon, produced by scripts/smil-to-waapi.js. The converter handles animateTransform rotates as CSS transform keyframes, opacity and fill-opacity as standard WAAPI keyframes, SVG geometry attributes (r, cx, cy, x, y, width, height) via a custom requestAnimationFrame wrapper because WAAPI’s CSS keyframes do not reach SVG presentation attributes, cubic-bezier easing from keySplines, and negative begin offsets via negative WAAPI delays.

A handful of icons that use complex sequential or additive animation timelines are hand-tuned in a LOCKED_ICONS map in the converter — blocks-shuffle-2, blocks-shuffle-3, 3-dots-move, fotogrids, and square-loader. The rest are generated automatically. A scripts/waapi-compare.html side-by-side viewer is produced as part of the same build so visual parity between the original SMIL version and the converted WAAPI version can be eyeballed when an icon is added or changed.

How animations start

For each Gallery on the page, the renderer (Loading_Icon::html_after in Plugin/src/public/render/features/loading-icon/class-loading-icon.php) emits a small inline <script> block immediately after the Gallery wrapper. That script runs synchronously as the browser parses the page — before any images have started loading from cache, and before DOMContentLoaded — and calls the WAAPI animate function on every loader SVG inside that Gallery.

The animation handles returned by WAAPI (and any requestAnimationFrame IDs returned by the geometry-attribute wrappers) are stored in a WeakMap keyed by the .fg-item element:

window.fgLoaderHandles  // WeakMap<HTMLElement, Animation[]>

This keeps the references reachable without holding the items in memory after they are removed from the DOM.

How animations stop

The footer script loading-icon.js is responsible for state management. For every <img> inside every Gallery it wires load and error listeners. When an image settles — either because it loaded successfully or because it failed — the script does two things on that specific item:

  • Reads the animation handles from window.fgLoaderHandles, calls cancel() on each WAAPI Animation, and calls cancelAnimationFrame on each rAF id. This frees the loader’s GPU work the moment it is no longer needed.
  • Sets data-fg-media-state="loaded" on the .fg-item element. CSS reads this attribute to hide the loader and reveal the image, optionally running the chosen Loaded Effect.

Animations are cancelled rather than left running because a Gallery with a hundred items would otherwise keep a hundred WAAPI Animations and rAF callbacks alive — measurable battery and CPU cost on lower-end devices, even though the loaders are no longer visible.

A MutationObserver watches document.body for Galleries inserted after the initial page load — AJAX Album transitions, password-unlock swaps, pagination load-more, dynamic block insertions — and runs the same start/stop wiring on the newly added items. Dynamically inserted items do not have a prior inline script, so the observer is also responsible for starting their animations.

Markup contract

If you need to render the loading icon outside FotoGrids’ own renderer, the minimum markup is:

<figure class="fg-item">
    <div class="fg-item-media">
        <svg class="fg-item-loader" ...><!-- icon SVG with SMIL stripped --></svg>
        <img src="..." alt="...">
    </div>
</figure>

Then, once the SVG is in the DOM, start its animation by calling the global animate function:

<script>
    window.fotogridsLoadingIcons['12-dots'](
        document.querySelector( '.fg-item-loader' )
    );
</script>

PHP helpers fotogrids_get_loading_icon_svg( $icon_name, $instance_id ) and fotogrids_get_loading_icon_animate_fn( $icon_name ) (both in Plugin/src/includes/functions-helpers.php) return the SVG string and the animate function string respectively. The SVGs include a __FG_ID__ placeholder that must be replaced with a per-item unique suffix to avoid gradient and clipPath ID collisions on Galleries with many loaders.

The animate functions are emitted as raw JavaScript on the page. They are read from the build-time JSON only — never from user input — so they are not eval’d at runtime, but if you customise the build pipeline make sure the SVG source files you add are themselves trusted.

Was this article helpful?