هناك سبب يجعل التأثير المنظوري بـJavaScript يبدو دائماً غير صحيح قليلاً — منفصلاً بعض الشيء، كأن الطبقات تسبح نصف إطار خلف الصفحة. هذا ليس كوداً رديئاً. السبب هو أن أحداث التمرير تصل إلى JavaScript بعد أن يكون المتصفح قد رسم التمرير بالفعل. حلّ المتصفح هذا مرة واحدة، حين حوّل الترويسات اللاصقة إلى position: sticky. Scroll-driven animations هي نفس الخطوة، مطبّقة على جميع التأثيرات المرتبطة بالتمرير دفعة واحدة.
لعشر سنوات، كان تحريك الصور المصغّرة إلى صور البطل يعني تقنية واحدة: قِس العنصر قبل وبعد، وزيّف الفرق بـtransform، ثم اعرضه. FLIP. كل مكتبة حركات تفعل ذلك، وكل منها تدفع ثمنه بـJavaScript على الخيط الرئيسي في أسوأ لحظة ممكنة. View Transitions API ينقل الحيلة بأكملها إلى المتصفح — ومنذ React 19.2 وNext.js 16، يتكون العقد بأكمله من خاصية واحدة اسمها name.
كتبتُ في مقال التفاعلات الدقيقة أن transform وopacity تتحرّكان على خيط التركيب (compositor)، فحتى الصفحة الثقيلة لا ينبغي أن تُعطّل حركة الزر. هذا لا يزال صحيحًا – مع تحفّظ واحد سكت عنه ذلك المقال: إذا كان الخيط الرئيسي محجوزًا بمهمّة JS طويلة، فقد لا تحصل الحركة أبدًا على فرصة للبدء، لأنّ معالج النقرة نفسه ينتظر في الطابور. INP هو مقياس يقيس بالضبط هذه الفجوة – وscheduler.yield() إحدى الوسائل القليلة لسدّها فعليًّا.
زرّ «إعجاب» يستجيب فورًا، القلب يمتلئ، العدّاد يرتفع – قبل أن يجيب الخادم أصلًا. هذا ليس وهم السرعة من مقال التفاعلات الدقيقة، بل شيء أبعد من ذلك: تُظهر الواجهة حالة لم توجد بعد، وتفترض أنها ستوجد قريبًا. أفحص كيف تُبنى هذه التقنية بأمان – مع تراجع حقيقي عن الكذبة حين لا يتحقّق الافتراض، ولماذا يُعدّ setState اليدوي مع catch نسخة أسوأ ممّا يقدّمه useOptimistic.
متجران يبيعان المنتج نفسه، وبنفس زمن استجابة الخادم – ومع ذلك يبدو أحدهما أسرع وأكثر جدارة بالثقة من الآخر. الفارق يكمن في التفاعلات الدقيقة: بضع مئات من المللي ثانية من الحركة تقرر ما إذا كان المستخدم سيثق بما يراه على الشاشة. أفكك الآلية من الأساس – نموذج المُحفّز/التغذية الراجعة، وما يحدث فعليًا داخل محرك العرض في المتصفح، وخاصية prefers-reduced-motion.
CSS Houdini ليست تقنية واحدة، بل مجموعة من المواصفات في مراحل نضج مختلفة تماماً. اكتشف أي جزء منها صالح اليوم للاستخدام في الإنتاج، وكيف يعمل @property وpaint worklet فعلياً، ولماذا لا يزال الباقي مجرد تجربة.