attention-choreography

با زمان‌بندی هوشمندانه، مسیر نگاه کاربر را در انیمیشن هدایت کنید.

این Skill ترتیب ورود و جابه‌جایی توجه بین عناصر رابط کاربری را بر اساس الگوهای F-pattern، Z-pattern، center-out، Gutenberg و layer-depth طراحی می‌کند. با تعریف Focus Anchorها، اهمیت بصری هر عنصر به زمان‌بندی و شدت انیمیشن تبدیل می‌شود.

برای landing page، hero section، فرم‌ها، modalها و محتوای متنی مناسب است و نمونه‌های اجرایی برای GSAP و Framer Motion ارائه می‌دهد. پشتیبانی از RTL و prefers-reduced-motion کمک می‌کند تجربه‌ای قابل‌فهم‌تر و دسترس‌پذیرتر بسازید.

چه چیزی می‌گیرید

  • انتخاب الگوی مناسب حرکت بر اساس نوع محتوا و چیدمان
  • تعریف اولویت‌های primary، secondary، tertiary و ambient برای عناصر
  • زمان‌بندی stagger و مسیرهای توجه با GSAP یا Framer Motion
  • الگوهای انتقال تمرکز مانند glide shift و bridge shift
  • راهکارهای RTL و حالت `prefers-reduced-motion`

برای چه کاری خوب است

  • طراحان و توسعه‌دهندگانی که برای landing page یا hero section موشن طراحی می‌کنند
  • پروژه‌هایی با محتوای متنی، کارت‌ها، فرم‌ها یا modalهای چندعنصری
  • تیم‌هایی که می‌خواهند hierarchy بصری و ترتیب نمایش عناصر را به‌صورت سیستماتیک تنظیم کنند

برای چه کاری نیست

  • ساخت انیمیشن‌های مستقل بدون نیاز به ترتیب‌دهی توجه یا سلسله‌مراتب بصری
  • پروژه‌هایی که به طراحی کامل visual identity یا تولید assetهای گرافیکی نیاز دارند
  • صفحه‌هایی که تمام عناصر باید هم‌زمان و بدون حرکت ترتیبی نمایش داده شوند

دربارهٔ این اسکیل

Attention Choreography

Guide visual attention through animation sequencing. Align animation order with natural reading patterns and visual hierarchy to create intuitive, scannable motion.

Standards Reference

StandardDocumentation
W3C DTCGdesigntokens.org
Nielsen Norman GroupF-pattern, Z-pattern research
Material Design 3m3.material.io/styles/motion
Gutenberg DiagramVisual gravity principles

Quick Reference

Load from: tokens/attention-path.tokens.json

Reading Patterns

PatternPrimary AxisBest ForStagger
F-patternHorizontalText-heavy, articles60ms
Z-patternDiagonalLanding pages, sparse80ms
center-outRadialHero sections, modals50ms
gutenbergDiagonalForms, cards70ms
layer-depthZ-axisParallax, 3D scenes100ms

Focus Anchors

LevelAttention WeightAnimation Priority
primary1.01 (first)
secondary0.62
tertiary0.33
ambient0.14 (last)

Selection Guide

What's the content type?

├── Text-heavy content (articles, docs)
│   └── F-PATTERN
│       └── Animate top-left → top-right → down-left
│
├── Sparse, marketing-focused (landing)
│   └── Z-PATTERN
│       └── Animate top-left → top-right → bottom-left → CTA
│
├── Focal point content (hero, modal)
│   └── CENTER-OUT
│       └── Animate center → inner ring → outer ring
│
├── Balanced layout (forms, cards)
│   └── GUTENBERG
│       └── Primary optical → strong fallow → terminal
│
└── Layered/3D content
    └── LAYER-DEPTH
        └── Foreground → midground → background

Pattern Implementations

F-Pattern

For text-heavy content with strong horizontal bias.

// F-pattern: top-left emphasis, decreasing attention downward
const fPatternConfig = {
  zones: {
    'top-left': { priority: 1, stagger: 0 },
    'top-right': { priority: 2, stagger: 60 },
    'mid-left': { priority: 3, stagger: 120 },
    'mid-right': { priority: 4, stagger: 180 },
    'bottom-left': { priority: 5, stagger: 240 }
  },
  attentionDecay: 0.15
};

function applyFPattern(elements) {
  const zones = categorizeByPosition(elements);

  Object.entries(fPatternConfig.zones).forEach(([zone, config]) => {
    const zoneElements = zones[zone] || [];

    gsap.from(zoneElements, {
      opacity: 0,
      y: 20,
      duration: 0.3,
      delay: config.stagger / 1000,
      stagger: 0.03,
      ease: 'power2.out'
    });
  });
}

function categorizeByPosition(elements) {
  const zones = {};
  const container = elements[0].parentElement.getBoundingClientRect();

  elements.forEach(el => {
    const rect = el.getBoundingClientRect();
    const relX = (rect.left - container.left) / container.width;
    const relY = (rect.top - container.top) / container.height;

    let zone;
    if (relY < 0.33) {
      zone = relX < 0.5 ? 'top-left' : 'top-right';
    } else if (relY < 0.66) {
      zone = relX < 0.5 ? 'mid-left' : 'mid-right';
    } else {
      zone = 'bottom-left';
    }

    if (!zones[zone]) zones[zone] = [];
    zones[zone].push(el);
  });

  return zones;
}

Framer Motion:

const fPatternVariants = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.06,
      staggerDirection: 1
    }
  }
};

// Assign data-priority based on position
<motion.div variants={fPatternVariants}>
  <motion.h1 data-priority="1" variants={itemVariants}>Headline</motion.h1>
  <motion.nav data-priority="2" variants={itemVariants}>Navigation</motion.nav>
  <motion.p data-priority="3" variants={itemVariants}>Content</motion.p>
</motion.div>

Z-Pattern

For landing pages and sparse layouts.

const zPatternConfig = {
  path: ['top-left', 'top-right', 'center', 'bottom-left', 'bottom-right'],
  emphasisZones: ['top-left', 'bottom-right'], // Logo and CTA
  staggerBase: 80
};

function applyZPattern(elements) {
  const zones = categorizeForZ(elements);
  let cumulativeDelay = 0;

  zPatternConfig.path.forEach((zone, index) => {
    const zoneElements = zones[zone] || [];
    const isEmphasis = zPatternConfig.emphasisZones.includes(zone);

    gsap.from(zoneElements, {
      opacity: 0,
      scale: isEmphasis ? 0.9 : 0.95,
      y: isEmphasis ? 30 : 15,
      duration: isEmphasis ? 0.4 : 0.3,
      delay: cumulativeDelay / 1000,
      ease: isEmphasis ? 'back.out(1.5)' : 'power2.out'
    });

    cumulativeDelay += zPatternConfig.staggerBase;
  });
}

Center-Out

For hero sections and modals with central focus.

const centerOutConfig = {
  rings: ['center', 'inner-ring', 'outer-ring', 'edges'],
  staggerPerRing: 50,
  intensityDecay: 0.2
};

function applyCenterOut(elements, containerCenter) {
  const rings = categorizeByDistance(elements, containerCenter);

  rings.forEach((ring, ringIndex) => {
    const intensity = 1 - (ringIndex * centerOutConfig.intensityDecay);

    gsap.from(ring, {
      opacity: 0,
      scale: 0.8 + (0.2 * (1 - intensity)),
      duration: 0.3 + (0.1 * intensity),
      delay: ringIndex * centerOutConfig.staggerPerRing / 1000,
      ease: 'power2.out'
    });
  });
}

function categorizeByDistance(elements, center) {
  const distances = elements.map(el => {
    const rect = el.getBoundingClientRect();
    const elCenter = {
      x: rect.left + rect.width / 2,
      y: rect.top + rect.height / 2
    };
    return {
      element: el,
      distance: Math.hypot(elCenter.x - center.x, elCenter.y - center.y)
    };
  });

  // Sort by distance and group into rings
  distances.sort((a, b) => a.distance - b.distance);

  const ringSize = Math.ceil(elements.length / 4);
  const rings = [];

  for (let i = 0; i < 4; i++) {
    rings.push(
      distances
        .slice(i * ringSize, (i + 1) * ringSize)
        .map(d => d.element)
    );
  }

  return rings;
}

Framer Motion:

function CenterOutContainer({ children }) {
  const containerRef = useRef(null);

  return (
    <motion.div
      ref={containerRef}
      variants={{
        hidden: {},
        visible: {
          transition: {
            staggerChildren: 0.05,
            staggerDirection: 1
          }
        }
      }}
    >
      {React.Children.map(children, (child, index) => {
        // Calculate ring based on distance from center
        const ring = Math.floor(index / 3); // Simplified

        return (
          <motion.div
            variants={{
              hidden: { opacity: 0, scale: 0.8 },
              visible: {
                opacity: 1,
                scale: 1,
                transition: { delay: ring * 0.05 }
              }
            }}
          >
            {child}
          </motion.div>
        );
      })}
    </motion.div>
  );
}

Gutenberg Diagram

For balanced layouts following natural visual flow.

const gutenbergConfig = {
  zones: {
    'primary-optical': { position: 'top-left', weight: 1.0 },
    'strong-fallow': { position: 'top-right', weight: 0.6 },
    'weak-fallow': { position: 'bottom-left', weight: 0.3 },
    'terminal-area': { position: 'bottom-right', weight: 0.9 }
  },
  readingGravity: 'diagonal'
};

function applyGutenberg(elements) {
  const zones = categorizeGutenberg(elements);

  // Primary optical area first
  gsap.from(zones['primary-optical'], {
    opacity: 0,
    y: 20,
    duration: 0.4,
    ease: 'power2.out'
  });

  // Strong fallow (often navigation)
  gsap.from(zones['strong-fallow'], {
    opacity: 0,
    y: 15,
    duration: 0.3,
    delay: 0.08,
    ease: 'power2.out'
  });

  // Terminal area (CTA)
  gsap.from(zones['terminal-area'], {
    opacity: 0,
    scale: 0.95,
    duration: 0.35,
    delay: 0.15,
    ease: 'back.out(1.5)'
  });

  // Weak fallow last
  gsap.from(zones['weak-fallow'], {
    opacity: 0,
    duration: 0.25,
    delay: 0.2,
    ease: 'power2.out'
  });
}

Focus Anchors

Establish visual hierarchy with explicit priority.

interface FocusAnchor {
  element: HTMLElement;
  level: 'primary' | 'secondary' | 'tertiary' | 'ambient';
  attentionWeight: number;
  entrancePriority: number;
}

const focusLevels = {
  primary: { weight: 1.0, priority: 1, intensity: 0.8 },
  secondary: { weight: 0.6, priority: 2, intensity: 0.6 },
  tertiary: { weight: 0.3, priority: 3, intensity: 0.4 },
  ambient: { weight: 0.1, priority: 4, intensity: 0.2 }
};

function animateByHierarchy(anchors: FocusAnchor[]) {
  // Sort by priority
  anchors.sort((a, b) => a.entrancePriority - b.entrancePriority);

  let cumulativeDelay = 0;

  anchors.forEach(anchor => {
    const config = focusLevels[anchor.level];

    gsap.from(anchor.element, {
      opacity: 0,
      y: 20 * config.intensity,
      scale: 1 - (0.1 * config.intensity),
      duration: 0.3,
      delay: cumulativeDelay,
      ease: 'power2.out'
    });

    cumulativeDelay += 0.08;
  });
}

Attention Shifts

Transitioning focus between elements.

Glide Shift

Smooth attention transfer.

function glideShift(from, to) {
  const tl = gsap.timeline();

  // Defocus current
  tl.to(from, {
    opacity: 0.6,
    scale: 0.98,
    duration: 0.15
  });

  // Focus new
  tl.to(to, {
    opacity: 1,
    scale: 1,
    boxShadow: '0 0 0 3px rgba(33, 150, 243, 0.3)',
    duration: 0.2
  }, '-=0.1');

  return tl;
}

Bridge Shift

Connected path between focus points.

function bridgeShift(from, to) {
  const fromRect = from.getBoundingClientRect();
  const toRect = to.getBoundingClientRect();

  // Create visual connector
  const connector = createConnector(fromRect, toRect);

  const tl = gsap.timeline();

  // Draw connector
  tl.from(connector, {
    strokeDashoffset: connector.getTotalLength(),
    duration: 0.3,
    ease: 'power2.inOut'
  });

  // Shift focus
  tl.to(from, { opacity: 0.5, duration: 0.15 }, '-=0.15');
  tl.to(to, { opacity: 1, scale: 1.02, duration: 0.2 });

  // Fade connector
  tl.to(connector, { opacity: 0, duration: 0.2 });

  return tl;
}

Distraction Management

Reduce competing attention during focus moments.

function suppressSecondary(primarySelector) {
  // Dim non-primary elements
  gsap.to('*:not(' + primarySelector + ')', {
    opacity: 0.6,
    filter: 'blur(1px)',
    duration: 0.2,
    ease: 'power2.out'
  });

  // Enhance primary
  gsap.to(primarySelector, {
    opacity: 1,
    filter: 'blur(0)',
    boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
    duration: 0.3
  });
}

function restoreAll() {
  gsap.to('*', {
    opacity: 1,
    filter: 'blur(0)',
    boxShadow: 'none',
    duration: 0.3
  });
}

Cultural Adaptations

RTL (Right-to-Left) Languages

function getPatternForDirection(pattern, direction) {
  if (direction === 'rtl') {
    const mirrorMap = {
      'f-pattern': {
        'top-left': 'top-right',
        'top-right': 'top-left',
        'mid-left': 'mid-right',
        'mid-right': 'mid-left'
      },
      'z-pattern': {
        'top-left': 'top-right',
        'top-right': 'top-left',
        'bottom-left': 'bottom-right',
        'bottom-right': 'bottom-left'
      }
    };

    return mirrorMap[pattern] || pattern;
  }

  return pattern;
}

Reduced Motion

For prefers-reduced-motion: reduce:

Full ChoreographyReduced
Staggered by patternSimultaneous fade
Attention shiftsInstant focus change
Bridge connectorsSkip
Hierarchy animationUniform timing
const prefersReducedMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

if (prefersReducedMotion) {
  // Skip choreography, simple fade
  gsap.from(elements, {
    opacity: 0,
    duration: 0.2,
    stagger: 0 // No stagger
  });
}

Cross-References

  • See sequencing/ for stagger patterns
  • See narrative-arc/ for story-aligned timing
  • See dramaturgy/ for scene composition
  • See entrance-patterns/ for element entrance animations

References

  • references/f-pattern.md — F-pattern implementation details
  • references/z-pattern.md — Z-pattern implementation details

دانلود ZIP