Advanced Targeting
Target specific pages with post types, custom RegEx URL patterns, and selector strategies.
Targeting controls which elements animate and which pages an animation runs on. This page covers compound CSS selectors, scoped targeting, dynamic elements, URL-based page matching, and WordPress post type targeting.
CSS Selector Strategies
The SDK accepts any valid CSS selector string as a TargetInput. Complex selectors let you target elements precisely without adding extra classes to your HTML.
Compound Selectors
Combine multiple selector rules to narrow the target:
import { Motion } from "@motion.page/sdk";
// Elements that have BOTH classes
Motion("combined", ".card.featured", {
from: { opacity: 0, y: 30 },
duration: 0.6,
}).onPageLoad();
// Direct children only
Motion("children", ".menu > .menu-item", {
from: { x: -20, opacity: 0 },
stagger: 0.06,
duration: 0.4,
}).onPageLoad();
// Adjacent sibling
Motion("sibling", ".hero + .intro", {
from: { opacity: 0 },
duration: 0.8,
}).onPageLoad(); :nth-child and Structural Selectors
Target elements by their position in the DOM:
// Every other card — skip the first
Motion("even-cards", ".grid .card:nth-child(even)", {
from: { y: 40, opacity: 0 },
duration: 0.5,
stagger: 0.07,
}).onScroll({ each: true });
// First three items only
Motion("first-three", ".list-item:nth-child(-n+3)", {
from: { opacity: 0, x: -20 },
duration: 0.5,
stagger: 0.08,
}).onPageLoad();
// Last item in a group
Motion("last-item", ".nav-item:last-child", {
to: { color: "#9966FF" },
duration: 0.3,
}).onHover({ onLeave: "reverse" }); Data Attribute Selectors
Target elements by their HTML data attributes — useful when you control the markup but not the class names:
// Any element with the attribute present
Motion("data-reveal", "[data-animate]", {
from: { opacity: 0, y: 20 },
duration: 0.5,
stagger: 0.06,
}).onScroll({ each: true });
// Attribute with a specific value
Motion("data-hero", "[data-role='hero']", {
from: { scale: 0.95, opacity: 0 },
duration: 0.8,
ease: "power3.out",
}).onPageLoad();
// Attribute starting with a prefix — useful for BEM modifiers
Motion("data-prefix", "[data-speed^='fast']", {
from: { y: 30, opacity: 0 },
duration: 0.3,
}).onPageLoad(); Selector Arrays
Pass multiple selectors to animate them as one group:
// All three animate together as a single timeline
Motion("multi", ["h1", ".subtitle", ".cta-button"], {
from: { opacity: 0, y: 30 },
duration: 0.6,
stagger: 0.1,
}).onPageLoad(); Scoped Targeting
Motion.utils.toArray(target, scope) resolves a selector only within a specific container. Use this when your page has repeated components with identical class names and you need to target only the one inside a certain parent.
import { Motion } from "@motion.page/sdk";
// Resolve .card only inside #featured-section
const cards = Motion.utils.toArray(".card", "#featured-section");
Motion("scoped-cards", cards, {
from: { opacity: 0, y: 30 },
duration: 0.6,
stagger: 0.08,
}).onPageLoad(); This prevents animations from leaking into other sections that share the same class names.
Scoping with Refs (React)
In React, pass the ref element as the scope to avoid stale global selectors:
import { useEffect, useRef } from "react";
import { Motion } from "@motion.page/sdk";
export function ProductGrid() {
const gridRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!gridRef.current) return;
const cards = Motion.utils.toArray(".card", gridRef.current);
Motion("grid-reveal", cards, {
from: { opacity: 0, y: 24 },
duration: 0.5,
stagger: 0.07,
ease: "power2.out",
}).onScroll({ each: true });
return () => Motion.kill("grid-reveal");
}, []);
return (
<div ref={gridRef} className="product-grid">
{/* cards rendered here */}
</div>
);
} Dynamic Element Targeting
Elements added after the page loads — via AJAX, pagination, or framework rendering — won’t be found by selectors that ran at init time. Use Motion.context() to scope animations so they can be reinitialized cleanly when the DOM changes.
Motion.context() for Reinit
import { Motion } from "@motion.page/sdk";
let ctx = Motion.context(() => {
Motion("cards", ".card", {
from: { opacity: 0, y: 30 },
duration: 0.5,
stagger: 0.07,
}).onScroll({ each: true });
});
// After AJAX inserts new .card elements:
ctx.refresh(); // kills old animations, re-runs init, re-resolves .card ctx.refresh() destroys the previous timelines and re-runs the factory function. All selectors are evaluated again against the updated DOM.
MutationObserver Pattern
Use a MutationObserver to detect when new elements appear and reinitialize:
import { Motion } from "@motion.page/sdk";
let ctx = Motion.context(() => {
Motion("dynamic-items", "[data-animate]", {
from: { opacity: 0, y: 20 },
duration: 0.4,
stagger: 0.06,
}).onPageLoad();
});
const observer = new MutationObserver(() => {
ctx.refresh();
});
// Watch for added nodes anywhere in the list container
observer.observe(document.querySelector("#results-list")!, {
childList: true,
subtree: true,
});
// Clean up when the page unloads
window.addEventListener("pagehide", () => {
observer.disconnect();
ctx.revert();
}); Performance tip: Debounce the observer callback if the DOM updates frequently in rapid bursts. Call
ctx.refresh()once after the burst settles rather than on every mutation.
Debounced Observer
import { Motion } from "@motion.page/sdk";
let ctx = Motion.context(() => {
Motion("feed-items", ".feed-item", {
from: { opacity: 0, y: 16 },
duration: 0.35,
stagger: 0.05,
}).onPageLoad();
});
let debounceTimer: ReturnType<typeof setTimeout>;
const observer = new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => ctx.refresh(), 150);
});
observer.observe(document.querySelector("#feed")!, {
childList: true,
subtree: true,
}); See SPA Integration for framework-specific lifecycle patterns.
Page-Specific Targeting (URL Matching)
Run an animation only on pages whose URL matches a pattern. Check window.location before registering the timeline.
Exact Path Match
import { Motion } from "@motion.page/sdk";
if (window.location.pathname === "/about") {
Motion("about-hero", ".hero-title", {
from: { opacity: 0, y: 40 },
duration: 0.8,
ease: "power3.out",
}).onPageLoad();
} RegEx URL Patterns
Use RegExp.test() to match URL patterns — slug fragments, query strings, or path segments:
import { Motion } from "@motion.page/sdk";
// Any URL containing "blog"
if (/blog/.test(window.location.pathname)) {
Motion("blog-reveal", ".post-card", {
from: { opacity: 0, y: 24 },
duration: 0.5,
stagger: 0.08,
}).onScroll({ each: true });
}
// Case-insensitive match — product pages
if (/\/products?\//i.test(window.location.pathname)) {
Motion("product-fade", ".product-image", {
from: { scale: 0.97, opacity: 0 },
duration: 0.6,
ease: "power2.out",
}).onScroll({ each: true });
}
// Homepage only (root path)
if (/^\/$/.test(window.location.pathname)) {
Motion("home-hero", "#hero", {
from: { opacity: 0, y: 60 },
duration: 1,
ease: "power3.out",
}).onPageLoad();
} Multiple Page Conditions
import { Motion } from "@motion.page/sdk";
const path = window.location.pathname;
const isLanding = /^\/(home|landing|index)/.test(path);
const isProduct = /\/product\//.test(path);
if (isLanding || isProduct) {
Motion("conversion-cta", ".cta-block", {
from: { opacity: 0, scale: 0.96 },
duration: 0.7,
ease: "back.out",
}).onScroll({ toggleActions: "play none none none" });
} WordPress: Post Type Targeting
In the Motion.page Builder, the Advanced Targeting panel lets you restrict a timeline to specific WordPress post types, template pages, or URL patterns — without writing any code.
How It Works
Open the Advanced Targeting panel in the Left Panel under the timeline name. Use the dropdown to select:
| Option | What it matches |
|---|---|
Post type slug (e.g. post, page, product) | All posts of that type |
$search | The WordPress search results page |
$404 | The 404 error page |
| Custom RegEx string | Any URL matching the pattern |
Selected post types are stored per timeline and evaluated at runtime — the animation only loads on matching pages.
RegEx in the Builder
The Advanced Targeting input also accepts custom RegEx strings. Enter the pattern without delimiters — the Builder wraps it automatically:
| Input | Matches |
|---|---|
about | Any URL containing “about” |
\/shop\/.*\/review | URLs like /shop/product-name/review |
\?ref=email | URLs with the ?ref=email query parameter |
Rules:
- Delimiters (
/…/) are added automatically — do not include them. - The only supported flag is
i(case-insensitive). Add it manually if needed. - Escape special regex characters with
\— e.g. use\.to match a literal dot.
Example: To match all pages with the slug
servicesorservice, enterservices?— the?makes the trailingsoptional.
WordPress Code Equivalent
If you’re using the SDK directly in a WordPress theme, replicate the same logic with window.location:
import { Motion } from "@motion.page/sdk";
// Equivalent to targeting the "product" post type
// WordPress adds the slug in the URL — match accordingly
if (/\/product\//.test(window.location.pathname)) {
Motion("product-reveal", ".entry-content", {
from: { opacity: 0, y: 20 },
duration: 0.6,
ease: "power2.out",
}).onPageLoad();
} For the full WordPress plugin targeting workflow, see the Builder documentation.
Tips and Gotchas
Selectors resolve at registration time. If elements don’t exist yet when Motion() is called, the timeline targets zero elements. Use Motion.context() with ctx.refresh() for late-rendered content.
each: true is per-element, not per-page. The each option on triggers creates independent timeline instances for each matched element. It is separate from page-level URL targeting.
Compound selectors with stagger. When using stagger across a compound selector, all matched elements animate in DOM order regardless of which sub-selector found them:
// All .card and .featured-card elements, staggered together in DOM order
Motion("mixed", [".card", ".featured-card"], {
from: { opacity: 0, y: 20 },
duration: 0.5,
stagger: 0.06,
}).onPageLoad(); Scoping prevents bleed. Always use Motion.utils.toArray(selector, scope) when the same component renders multiple times on one page. Without a scope, .card matches every .card on the page.
Related
- Motion() Factory —
TargetInputtypes andMotion.utils.toArray() - Scroll Trigger —
each: truefor per-element scroll instances - SPA Integration —
Motion.context()for framework lifecycle management - Responsive Animations —
matchMediafor screen-size targeting