كل التدويناتAll posts

حركة تخدم الموقع مش تبوظه: قواعدي مع GSAPMotion that helps, not hurts: my GSAP rules

ست قواعد بمشي عليها في أي حركة: المحتوى يظهر دايما، السكرول هو اللي يسوق، والصورة ما تتشوهش.Six rules for every animation: content always shows, scroll drives, and the portrait is never distorted.

  • الكاتبWritten byزياد شلبيZeyad Shalaby
  • التاريخDate
  • وقت القرايةReading time3 دقايقminutes
حركة تخدم الموقع مش تبوظه: قواعدي مع GSAP

الحركة في الموقع زي الملح في الأكل: القليل بيفرق، والكتير من غير حساب بيبوظ كل حاجة. دي القواعد اللي بمشي عليها في أي موقع ببنيه بـ GSAP، وكلها طالعة من مشاكل قابلتها فعلا.

المحتوى يظهر حتى لو الحركة ما اشتغلتش

أول قاعدة وأهمها. لو خبيت العناصر في الـ CSS واستنيت الجافاسكريبت يظهرها، أي غلطة في السكربت أو مكتبة ما اتحملتش معناها صفحة فاضية.

الأضمن إن الصفحة تتكتب ظاهرة عادي، والحركة هي اللي تبدأ من حالة مخفية وترجع للوضع الطبيعي:

// العنصر ظاهر في الـ CSS. الحركة بتبدأه من تحت وترجعه مكانه
gsap.from('.card', {
  y: 40,
  autoAlpha: 0,
  duration: 0.9,
  ease: 'power4.out',
  stagger: 0.08,
  clearProps: 'all' // تنضف أي ستايل اتحط بعد ما تخلص
});

لو GSAP ما اتحملش، السطر ده ما بيتنفذش والكروت بتفضل ظاهرة زي ما هي.

احترم اللي قافل الحركة

فيه ناس الحركة الكتير بتتعبهم فعلا، وأنظمة التشغيل فيها إعداد لتقليل الحركة. المتصفح بيقولك عليه، ولازم تسمع:

const calm = matchMedia('(prefers-reduced-motion: reduce)').matches;
const anim = !!window.gsap && !calm;

if (anim) {
  // كل الحركة هنا
}

بعمل المتغير ده مرة واحدة في أول الملف، وكل حركة بعد كده بتعدي عليه. اللي قافل الحركة بيشوف الصفحة في شكلها النهائي على طول، من غير ما يخسر أي محتوى.

حرك اللي المتصفح بيعرف يحركه بسرعة

مش كل خاصية في الـ CSS زي التانية. تغيير transform و opacity رخيص على المتصفح. تغيير width أو top أو margin بيخليه يعيد حساب مكان كل حاجة في الصفحة مع كل فريم.

  • عايز تحرك عنصر؟ استخدم x و y مش left و top.
  • عايز تكبره؟ استخدم scale مش width.
  • عايز تخفيه؟ استخدم autoAlpha، بيظبط الشفافية والـ visibility مع بعض.

اربط الحركة بالسكرول، مش بالوقت

الحركة اللي بتشتغل لوحدها وتفضل تلف بتشتت. الحركة المربوطة بالسكرول بتحسس الزائر إنه هو اللي بيحركها، ولو وقف بتقف معاه.

gsap.to('.hero', {
  scale: 0.92,
  borderRadius: 56,
  ease: 'none',
  scrollTrigger: {
    trigger: '.hero',
    start: 'top top',
    end: 'bottom top',
    scrub: true // الحركة ماشية مع السكرول بالظبط
  }
});

نفس الفكرة مع الشريط اللي فيه أسماء أو كلام بيتحرك: خليه يتحرك مع السكرول بس، مش شريط بيلف لوحده للأبد.

الصورة الشخصية ما تتشوهش

أي تأثير بيمط أو يلوي صورة وش إنسان شكله بيبقى غلط مهما كان التأثير حلو. القاعدة عندي: الصورة نفسها ثابتة ومش مقصوصة، واللي بيتحرك هو اللي حواليها.

  • إطار ورا الصورة بيتحرك مع الماوس.
  • ضوء خفيف فوقها بيمشي مع المؤشر.
  • ختم دائري بيلف، وبيسرع لما السكرول يسرع.

حرك اللي حوالين الصورة، وسيب الصورة في حالها.

وقف اللي مش باين

خلفية WebGL شغالة تحت الفوتر والزائر لسه في أول الصفحة بتاكل بطارية على الفاضي. بوقف الرسم لما العنصر يخرج من الشاشة وأرجعه لما يدخل:

new IntersectionObserver(([entry]) => {
  visible = entry.isIntersecting;
  if (visible && !running) loop();
}).observe(canvas);

قايمة المراجعة قبل ما أسلم

  • الصفحة مقروءة كلها والجافاسكريبت مقفول؟
  • مع إعداد تقليل الحركة، كل المحتوى ظاهر؟
  • الحركة بتستخدم transform و opacity بس؟
  • مفيش حاجة بتتحرك لوحدها من غير سبب؟
  • الخلفيات التقيلة بتقف وهي بره الشاشة؟

Motion on a site is like salt in food: a little changes everything, too much ruins it. These are the rules I follow on anything I build with GSAP, and each one comes from a problem I actually hit.

Content shows even when motion fails

The first rule and the most important. If you hide elements in CSS and wait for JavaScript to reveal them, one script error or one library that failed to load means an empty page.

It is safer to write the page visible, and let the animation start from a hidden state and return to normal:

// The element is visible in CSS. The animation starts it lower and brings it home
gsap.from('.card', {
  y: 40,
  autoAlpha: 0,
  duration: 0.9,
  ease: 'power4.out',
  stagger: 0.08,
  clearProps: 'all' // remove inline styles once done
});

If GSAP does not load, that line never runs and the cards simply stay visible.

Respect people who turn motion off

Heavy motion really does make some people unwell, and operating systems have a reduce-motion setting. The browser tells you about it, so listen:

const calm = matchMedia('(prefers-reduced-motion: reduce)').matches;
const anim = !!window.gsap && !calm;

if (anim) {
  // all motion lives here
}

I create that flag once at the top of the file and every animation checks it. People with motion off see the page in its final state right away, without losing any content.

Animate what the browser animates cheaply

CSS properties are not equal. Changing transform and opacity is cheap. Changing width, top or margin makes the browser recalculate the layout of the page on every frame.

  • Moving something? Use x and y, not left and top.
  • Growing it? Use scale, not width.
  • Hiding it? Use autoAlpha, which handles opacity and visibility together.

Tie motion to scroll, not to the clock

Motion that runs by itself and keeps looping is a distraction. Motion tied to scroll feels like the visitor is driving it, and when they stop, it stops.

gsap.to('.hero', {
  scale: 0.92,
  borderRadius: 56,
  ease: 'none',
  scrollTrigger: {
    trigger: '.hero',
    start: 'top top',
    end: 'bottom top',
    scrub: true // progress follows the scroll exactly
  }
});

The same goes for a band of moving names or text: let it move with the scroll only, not loop forever by itself.

A portrait is never distorted

Any effect that stretches or bends a human face looks wrong, however nice the effect. My rule: the photo stays fixed and uncropped, and what moves is what surrounds it.

  • A frame behind the photo that shifts with the pointer.
  • A soft light on top that follows the cursor.
  • A circular stamp that rotates, faster when the scroll is faster.

Move what is around the photo, and leave the photo alone.

Pause what cannot be seen

A WebGL background running under the footer while the visitor is still at the top burns battery for nothing. I stop drawing when the element leaves the screen and resume when it returns:

new IntersectionObserver(([entry]) => {
  visible = entry.isIntersecting;
  if (visible && !running) loop();
}).observe(canvas);

My checklist before handing over

  • Is the whole page readable with JavaScript off?
  • With reduce-motion on, is all content visible?
  • Does the motion use transform and opacity only?
  • Does nothing move by itself without a reason?
  • Do heavy backgrounds pause off screen?

التعليقاتComments

سيب تعليقLeave a comment

اقرا كمانRead next