Chromium 151 · Lenis 1.1.20 · 2026-08-26
هل تنكسر روابط المِرساة فعلًا مع التمرير السلس؟
الشكوى سهلة الوجود، وتُصاغ دائمًا تقريبًا بوصفها حقيقة: ثبِّت مكتبة تمرير سلس فتتوقّف الروابط داخل الصفحة عن العمل. وبالقياس مقابل التثبيت الدقيق لهذا الموقع — Lenis 1.1.20، منسوخًا داخل المستودع بايتًا ببايت — في Chromium 151.0.7922.34، هذا القول خاطئ كتعميم، وشكل الخطأ هو الجزء المفيد فيه. فمع خيار anchors على قيمته الافتراضية false والصفحة ساكنة، تفعل النقرة على رابط داخل الصفحة كل ما يُفترض بالنقرة أن تفعله.
العنوان يكتسب الهاش، و:target يطابق، والصفحة تستقرّ عند 1,336 px مع احترام scroll-margin-top المُصرَّح به على الهدف، وضغطة Tab واحدة بعدها تُتابع داخل القسم بدل أن ترتدّ إلى الرابط. لا شيء مكسور لأن لا شيء اعتُرِض — فمُعالِج المِرساة في المكتبة لا يحتوي على preventDefault إطلاقًا، وتنقّل الجزء الخاص بالمتصفّح يجري تحته.
هناك حالة واحدة بالضبط يفشل فيها، ويبلغها القارئ في نحو ثانية. فبينما تكون حركة Lenis جارية بالفعل، تصل النقرة في منتصف المنحنى: على صفحة الاختبار كانت العجلة قد ضبطت هدفًا عند 2,500 px، وهبطت النقرة عند 1,052 px مع قراءة isScrolling بقيمة smooth، فنفّذ المتصفّح قفزته إلى 1,336، ثم كتب إطار الحركة التالي فوقها. استقرّت الصفحة عند 2,500 والهاش الصحيح في شريط العنوان — عنوان يكذب على القارئ بشأن موضعه بمقدار 1,164 px.
| التهيئة | العنوان بعد النقرة | يستقرّ عند (px) |
|---|---|---|
| بلا مكتبة، ولا اعتراض | الهاش مضبوط، و:target يطابق | 1,336 |
| anchors بقيمة false، نقرة والصفحة ساكنة | الهاش مضبوط، و:target يطابق | 1,336 |
| anchors بقيمة false، نقرة أثناء الحركة | الهاش مضبوط، و:target يطابق | 2,500 |
| anchors بقيمة true، نقرة والصفحة ساكنة | الهاش مضبوط، و:target يطابق | 1,456 |
| anchors بقيمة true، نقرة أثناء الحركة | الهاش مضبوط، و:target يطابق | 1,456 |
الآليّة سطر واحد. فـ Lenis يوفّق بين موضعه الداخلي وإزاحة التمرير الحقيقية للمستند داخل حارسٍ يشترط أن تكون isScrolling بقيمة false أو السلسلة native — عند lenis.mjs:640 في النسخة المنسوخة داخل المستودع. وأثناء الحركة تُقرأ قفزة الجزء التي ينفّذها المتصفّح على أنها ضجيج فتُهمَل، ويعيد الإطار الذي يليها كتابة رقم المكتبة نفسه. والمسألة رقم 150 في متتبّع المشروع تصف العَرَض من الخارج، بكلمات المُبلِّغ: الصفحة لا تنتقل إلى الموضع المطلوب إلا بعد أن تتوقّف حركة التمرير.
الصفّان الرابع والخامس هما العلاج الذي يحمل عيبه معه. فضبط anchors على true يُصلح التوقيت ويُنزل القارئ 120 px أخفض مما يفعل المتصفّح، لأن ذلك المسار يحلّ العنصر إلى أعلى مستطيله المحيط زائدًا التمرير المُحرَّك، ولا يقرأ scroll-margin-top أبدًا. عيب يُستبدل بعيب، وهذا هو النمط الذي تدور حوله المقالة كلّها.
إحساس الحركة التي يركبها هذان الصفّان — أُطر الاستقرار، وذروة السرعة، وما تكلّفه وهي متوقّفة — مقيسٌ في Lenis مقابل التمرير الأصلي، بالقياس.
HTML Standard §7.4.6.4, read 2026-08-26
ماذا يفعل المتصفّح حين تنقر على هاش؟
خمسة أشياء، وواحد منها فقط هو التمرير. فخوارزمية التمرير إلى الجزء في معيار HTML قصيرة بما يكفي لقراءتها في دقيقة، وهي المواصفة التي ينافسها كل مُعالِج مكتوب باليد، غالبًا من دون أن يكون قد قرأها.
والخطوة التي لا يتوقّعها أحد هي الأخيرة. فبالقياس على الصفّ الضابط، تترك نقرة الجزء الأصلية document.activeElement على عنصر body — إذ لا ينتقل التركيز إلى هدف غير قابل للتركيز — ومع ذلك تهبط ضغطة Tab التالية على رابط داخل القسم لا بعد الرابط الذي نُقر عليه. لقد نقل المتصفّح نقطة بدء التنقّل التسلسلي بالتركيز، وهي حالة منفصلة لا تكشفها أي خاصيّة في DOM.
- اضبط عنصر الهدف في المستند هذه هي الحالة التي يقرأها الصنف الزائف :target. تُضبط هنا ولا تُضبط في أي مكان آخر — لا بالعنوان، ولا بـ pushState، ولا بضبط location.hash من سكربت.
- شغّل خوارزمية كشف الأسلاف افتح عنصر details مغلقًا يحوي الهدف، واكشف المحتوى المُعلَّم بـ hidden-until-found. فالتمرير إلى إحداثي عند عنصر داخل قسم مطوي يصل إلى هدف ما يزال غير مرئي.
- مرّر الهدف إلى داخل الرؤية بقيمة behavior auto، وblock start، وinline nearest — ومع احترام منطقة الالتقاط (scroll snap area) للعنصر بدل صندوق حدوده، وهنا يدخل scroll-margin. وهذه هي الخطوة الوحيدة التي يُعيد أحدٌ بناءها.
- شغّل خطوات التركيز مع إطار عرض المستند بوصفه الهدف الاحتياطي. وفي حالة عنصر section عادي يقع الاحتياطي، ولهذا تُقرأ activeElement على أنها body بعدها.
- انقل نقطة بدء التنقّل التسلسلي بالتركيز هذه تقرّر إلى أين تذهب ضغطة Tab التالية. وهي غير مرئية لـ activeElement، وغير مرئية لأحداث التركيز، وهي سبب كون المِرساة الأصلية سهلة الوصول أصلًا بلا tabindex على الهدف.
- وخارج الخوارزمية، سجّل التنقّل نقرة الجزء تنقّلٌ داخل المستند نفسه: العنوان يتغيّر وتظهر مدخلة في سجلّ الجلسة، فيعيدك الرجوع إلى الهاش السابق لا إلى الصفحة السابقة.
قاس Hidde de Vries التباين نفسه في 2017 وقاله بوضوح: التركيز نُقل إلى body لا إلى الهدف، والمتصفّح مع ذلك يتذكّر موضع العنصر المرتبط، فيمضي التنقّل اللاحق بلوحة المفاتيح من هناك. والحدّ الفاصل لم يُحسم حتى الآن — فمستودع HTML يحمل ثلاثة نقاشات مفتوحة حول تنقّل الجزء وترتيب التركيز.
وتلك الفجوة بين ما تقوله activeElement وما يفعله Tab هي الطريق الذي تتحوّل به النيّة الحسنة إلى تراجع. يكتب مطوّر اختبارًا، ويقرأ activeElement بعد نقرة مِرساة أصلية، فيرى عنصر body، فيستنتج أن المتصفّح مكسور، ثم يُصلحه إلى شيء أسوأ منه فعلًا. والقسم التالي هو قياس مقدار هذا السوء.
4 handlers · 1200×800 · one Tab press
ما تكلفة preventDefault بالضبط؟
وُضِعت أربعة مُعالِجات على صفحة واحدة، كلٌّ منها مربوط برابط مطابق يشير إلى هدف مطابق، وقيس كلٌّ منها على ستّ قراءات: أين انتهت الصفحة، وماذا قال العنوان، وهل ظهرت مدخلة في السجلّ، وهل طابق :target، وماذا حملت document.activeElement، وإلى أين ذهبت ضغطة Tab واحدة بعدها. والعمود الأخير هو الذي لا يظهر أبدًا في التدوينات.
الصفّ A هو المتصفّح بلا أي اعتراض. والصفّ B هو المقتطف الذي يعود أولًا من كل بحث. والصفّ C هو النسخة المصحَّحة التي يشحنها الناس بعد أن يُبلّغ أحدهم عن خلل في العنوان. والصفّ D هو الشكل الذي يستعمله هذا المستودع، في 131 سطرًا داخل src/runtime/scroll/anchors.js.
| المُعالِج | العنوان، والسجلّ، و:target | ضغطة Tab التالية تهبط على | يهبط عند (px) |
|---|---|---|---|
| A — الافتراضي في المتصفّح، بلا اعتراض | الهاش مضبوط، الطول 3، و:target يطابق | الرابط داخل القسم | 1,336 |
| B — preventDefault مع scrollIntoView بقيمة behavior smooth | لا هاش، الطول 2، و:target فارغ | الرابط الذي يلي المنقور | 1,336 |
| C — preventDefault مع pushState مع scrollTo | الهاش مضبوط، الطول 3، و:target فارغ | الرابط الذي يلي المنقور | 1,456 |
| D — الصفّ C مع focus بقيمة preventScroll وtabindex مؤقّت | الهاش مضبوط، الطول 3، و:target فارغ | الرابط داخل القسم | 1,456 |
الصفّ D وحده يُعيد إنتاج المتصفّح، ويحتاج إلى استدعاء focus صريح ليفعل ذلك، لأن نقطة بدء التنقّل التسلسلي بالتركيز لا يمكن ضبطها من سكربت. ونقل التركيز الحقيقي إلى الهدف هو أقرب بديل متاح: يضع القارئ داخل القسم، وهو ما يسمّيه تعليق الوحدة في هذا المستودع نفسه الجزءَ الذي يُخطئ فيه كل تنفيذ للتمرير السلس.
ولاحظ ما لا يستعيده الصفّ D بعد. فـ :target فارغ في كل صفّ معترِض، بما في ذلك الجيّد منها، والصفحة تهبط 120 px أخفض مما يفعل المتصفّح. وكلاهما كلفة دائمة للاعتراض، والأمانة تقتضي تسميتهما لا ترك الصفّ يُقرأ مكسبًا نظيفًا.
src/runtime/scroll/anchors.js:70-96
إعادة كتابة الهاش تستعيد العنوان ولا شيء غيره
pushState لا replaceState، لأن الرجوع ينبغي أن يعود إلى الهاش السابق لا إلى الصفحة السابقة، وهذا هو سلوك المِرساة الحقيقية. وهذه الوحدة تكتبه أولًا، قبل التمرير وقبل نقل التركيز، والترتيب هنا تحمّلٌ للفشل لا مسألة ذوق: فالعنوان هو ما ينسخه القارئ ويتبعه الزاحف، لذلك يُضبط قبل أي شيء قد يرمي خطأً.
والأمران اللذان لا يفعلهما pushState مقيسان كلاهما. فهو يُطلق hashchange صفر مرّة — والمواصفة صريحة في أن خطوات تحديث العنوان والسجلّ ليست تنقّلًا ولا اجتيازًا للسجلّ — فيتوقّف أي مستمع يراقب hashchange ليتفاعل مع الحركة داخل الصفحة يوم يبدأ مُعالِج بالاعتراض. وهو يترك :target فارغًا، في Chromium 151 وWebKit 26.5 على السواء، لأن عنصر الهدف في المستند تضبطه خوارزمية الجزء ولا يضبطه شيء سواها.
event.preventDefault()
// السجلّ أولًا، ليبقى العنوان صحيحًا حتى لو رمى أي شيء تحته خطأً، و
// ليعود الرجوع إلى الهاش السابق لا إلى الصفحة السابقة.
if (view.location.hash !== `#${id}`) {
view.history.pushState(null, '', `#${id}`)
}
// تحت `reduce` هذه قفزة بلا استيفاء — الرحلة نفسها،
// بلا سفر.
scroll.scrollTo(target, { immediate: env.prefersReducedMotion })
moveFocusTo(target)
// الرجوع والتقدّم بين الهاشات يجب أن يُحرّكا الصفحة أيضًا: فـ pushState
// أعلاه يعني أن المتصفّح لن يُمرّر نيابةً عنّا.
function onPopState() {
const id = decodeURIComponent(view.location.hash.slice(1))
if (!id) return
const target = doc.getElementById(id)
if (!target) return
scroll.scrollTo(target, { immediate: true })
moveFocusTo(target)
}
مُعالِج popstate موجود لأن pushState سلب الاجتياز من المتصفّح، وهو يستعمل حركة فورية بدل حركة مُتدرّجة عن قصد: فالرجوع ليس رحلة، بل تصحيح، وتحريكه يجعل القارئ يشاهد ألف بكسل من المشهد ليتراجع عن خطأ. ولغة المواصفة هنا متساهلة في الاتّجاهين — على وكيل المستخدم أن يحاول استعادة موضع تمرير المدخلة، وأن يعود من دون استعادة متى كان المستخدم قد مرّر المستند — وهذا بالضبط ما يجعل كتابة المُعالِج دفاعًا مشروعًا لا تكرارًا زائدًا.
وثمّة حالة أضيق تستحقّ الفصل. فمؤشّر التمرير الذي يعيد كتابة الهاش كلّما مرّ القارئ بعنوان ينبغي أن يستعمل replaceState لا pushState: فهو يتعقّب موضعًا لا يسجّل وجهة، وملء زرّ الرجوع بعشرين مدخلة لم يخترها القارئ عيبٌ بذاته.
src/runtime/scroll/anchors.js:104-131
كيف تنقل التركيز من دون أن تُقاتل حركتك أنت؟
ثلاث تفاصيل، وكلٌّ منها موجود بسبب فشل مقيس. فـ tabindex بقيمة سالب واحد يُضاف فقط حين يكون العنصر عاجزًا أصلًا عن أخذ التركيز، حتى لا يُمنح هدفٌ من نوع رابط أو زرّ سِمةً لا يحتاجها. واستدعاء focus يمرّر preventScroll، لأن المتصفّح من دونه ينتقل إلى العنصر فورًا فلا يبقى للحركة التي بدأت للتوّ ما تُحرّكه. والسِّمة تُزال مجدّدًا عند blur، حتى لا تبقى في DOM حالةٌ كتبها زمن التشغيل ولم ينظّفها.
وهذه الأخيرة قاعدة في عقد سهولة الوصول لهذا المستودع، K-8، وهي قاعدة بسبب عيب في الموقع القديم: فـ tabindex بقيمة سالب واحد في لوحة القائمة كان يُضاف عند الفتح ولا يُعكس أبدًا. وثمّة حجّة خارجية للشكل نفسه — إذ أُبلغ أن tabindex دائمًا بقيمة سالب واحد على عنصر main يكسر زرّ الرجوع على iOS، وهذا سبب وجيه لإمساك السِّمة بقدر ما يدوم التركيز لا أكثر.
function moveFocusTo(target) {
// لا تستعِر من القابلية للتركيز إلا ما لا يملكه العنصر أصلًا.
const focusable =
target.hasAttribute('tabindex') || isNativelyFocusable(target)
if (!focusable) target.setAttribute('tabindex', '-1')
// preventScroll حامل للحِمل: من دونه يقفز المتصفّح إلى
// العنصر فلا يبقى للحركة التي بدأتها هذه الوحدة ما تُحرّكه.
target.focus({ preventScroll: true })
// K-8: كل ما يكتبه زمن التشغيل، يزيله زمن التشغيل.
if (!focusable) {
target.addEventListener('blur', function drop() {
target.removeAttribute('tabindex')
}, { once: true })
}
}
تبيّن أن رسم حلقة حول التركيز المنقول يتوقّف على طريقة تفعيل الرابط، والمحرّكات تختلف في ذلك. ففي Chromium 151 تترك نقرة الفأرة الهدف مركَّزًا مع :focus-visible بقيمة false وبلا مخطّط، بينما يتركه Tab ثم Enter مركَّزًا مع :focus-visible بقيمة true والمخطّط مرسوم — وهذا هو السلوك المطلوب، بلغناه من دون كتابة قاعدة له. أما WebKit 26.5 بلا واجهة فقد رسم الحلقة على مسار الفأرة أيضًا؛ وهذا التباين غير مُتحقَّق منه مقابل Safari المنشور، وFirefox لم يُقَس أصلًا، فعامِل صفّ Chromium بوصفه المقيس واعتبر ما عداه مفتوحًا.
والعقد الذي كُتبت عليه هذه الوحدة له قاعدة لذلك أيضًا. K-7: التركيز الذي ينقله زمن التشغيل يجب أن يكون قابلًا للإثبات بقراءة document.activeElement بعد ضغطة مفتاح حقيقية، لا بكون focus قد استُدعي. وقد دفع الموقع القديم ثمن هذه الجملة بحلقة إعادة محاولة من ثلاثين إطارًا لم توجد إلا لأن focus يفشل بصمت على عنصر يعبر حالة visibility hidden — نصف ثانية من الاستطلاع للالتفاف على استدعاء أبلغ بالنجاح ولم يفعل شيئًا.
3 declarations · 120 px · 2026-08-26
لماذا يهبط القسم تحت الترويسة الثابتة رغم ذلك؟
لأن scroll-margin خاصيّة لتمرير عنصر إلى داخل الرؤية، والتمرير إلى إحداثي لا يُعطى عنصرًا. فمواصفة CSS Scroll Snap تطلب من وكلاء المستخدم أن يستعملوا منطقة الالتقاط للعنصر بدل صندوق حدوده وحده عند تقرير ما يُجلب إلى الرؤية، حتى حين يكون الالتقاط مُطفأً — وهذا يشمل تنقّل الجزء الخاص بالمتصفّح ويشمل scrollIntoView. أما تمرير رقم إلى window.scrollTo فلا يعطي المتصفّح شيئًا يطبّق عليه هامشًا.
والفجوة هي الهامش المُصرَّح به بالضبط، وهي الفجوة نفسها في كلا المحرّكين اللذين قيسا.
- 1,336 رابط عميق وscrollIntoView كلاهما يحترم منطقة الالتقاط
- 1,456 scrollTo مع إحداثي الرقم لا هامش له يُقرأ
- 120 الفجوة، بالبكسل القيمة المُصرَّح بها بالضبط، في كل مرّة
- 3 تصريحات في هذا المستودع ومسار النقر لا يقرأ أيًّا منها
هذا هو القسم الذي تسمّي فيه المقالة عيبًا في بيتها. فالتصريحات الثلاثة كلّها تقع في أوراق أنماط أقسام — الوثائق القانونية وصفحات دراسات الحالة — والتعليق بجانب أحدها يقول السبب: مُصرَّح بها على الأقسام لا في زمن تشغيل التمرير، لتصمد ولا شيء يعمل. وهذا التعليل صحيح، وهو بعينه سبب إخفاق مسار النقر المُحسَّن في بلوغها. فكل تصريح من تلك التصريحات يبلغه رابط عميق، وإعادة تحميل، وصفحة بلا JavaScript، ولا يبلغه أيٌّ منها نقرة، لأن النقرة تنتهي بتمرير إلى إحداثي محلول بلا إزاحة مُمرَّرة. مؤكَّد بقراءة مسارَي الشيفرة كليهما؛ أما حجم الفجوة على المسارات الحيّة فلم يُقَس، والمقيس هو 120 px على الصفحة الاصطناعية وحدها.
والتدقيق الجنائي للموقع القديم تنبّأ بهذا الشكل بالضبط وسجّله فخًّا غير مختبَر: نقرة داخل الصفحة ورابط عميق إلى المُعرّف نفسه يهبطان عند إزاحتين مختلفتين، لأن مسار النقر يستعمل ارتفاع ترويسة محسوبًا بينما تستعمل CSS خاصيّة scroll-margin-top. نظاما إزاحة لا يجتمعان أبدًا.
وإن طرحت ارتفاع الترويسة بنفسك، فقِسه ولا تفترضه. فرمز المسافات في هذا المستودع كان يُظنّ أنه ارتفاع الترويسة إلى أن فُحص: قِيس الشريط عند 79.2 px مقابل 70.1 px للرمز عند عرض 390، و68.8 px مقابل 59.1 px عند 768. وصفحة تواصل بُنيت على ذلك الافتراض ظهرت تحت الشريط.
وصفحات دراسات الحالة هي حيث تؤدّي تلك التصريحات معظم عملها — ثماني مراسي فصول وشريط لاصق في كلٍّ من الأعمال العشرة.
src/runtime/scroll/scroll-controller.js:37-42
ماذا يغيّر تقليل الحركة في المِرساة؟
يزيل السفر ويُبقي الرحلة. فتحت تفضيل reduce تظل المِرساة تُحرّك الصفحة، وتظل تكتب الهاش، وتظل تنقل التركيز — إنما تصل فورًا، والضمان مكتوب في أربعة مواضع مستقلّة حتى لا يكون أيٌّ منها وحده هو ما يفشل.
وسبب هذا التكرار أن التمرير السلس تحت reduce ليس تجاوزًا تجميليًّا؛ إنه الحركة المحدَّدة التي طلب الزائر ألّا يراها، يقدّمها التفاعل الوحيد في الصفحة المضمون أن يقطع مسافة طويلة.
- immediate على مسار المِرساة
- مُعالِج النقر يمرّر تفضيل reduce كما هو راية immediate، فيخدم مسار الشيفرة نفسه الحالتين، ولا يبقى فرع ثانٍ يُنسى.
- behavior auto، لا smooth أبدًا
- المسار الأصلي في المتحكّم يُمرّر بقيمة behavior auto ولا يستعمل smooth أبدًا، لأن تمريرًا برمجيًّا سلسًا يُعيد بالضبط ما طلب التفضيل إزالته.
- scroll-behavior بقيمة initial
- ورقة أنماط المستند تُصرّح بـ scroll-behavior بقيمة initial لا smooth، فلا يوجد مُحرِّك ثانٍ غير قابل للإلغاء على الإزاحة نفسها ليقاتله زمن التشغيل.
- طبقة التصفير، مع علامة تعجّب
- كتلة reduce الشاملة تعيش في طبقة التصفير حتى لا يتجاوزها شيء بالخصوصية عرَضًا، وهي تفرض scroll-behavior على auto براية important.
- لا مكتبة أصلًا
- تحت reduce لا يُنشئ هذا المشروع المُمرِّر السلس إطلاقًا. لا شيء يعمل ليجعل مِرساةً تنزلق، وهذا أقوى شكل يمكن أن يتّخذه الضمان.
وقد احترم الموقع القديم التفضيل نفسه بطريق يستحقّ التذكّر. فقد استطلع ستّمئة إطار حركة انتظارًا لظهور متغيّر عام ثم دمّر النسخة — لأن الاستدعاء البديهي، أي إيقافها، قيس فتبيّن أنه يترك الصفحة غير قابلة للتمرير إطلاقًا: صفر بكسل على العجلة، وصفر بكسل على مفتاح End. تفضيل نُفِّذ هدمًا لا إيقافًا مؤقّتًا، لأن الإيقاف المؤقّت كان أسوأ من الحركة.
وللتفضيل نفسه مهمّة أصعب بكثير حين تكون الحركة لوحة رسم لا إزاحة تمرير — وتلك الحجّة في تقليل الحركة في WebGL وRive واللوحة.
a05-scroll.md §12 · 689 anchors
ما الذي ينبغي أن ترفض بناءه؟
أربعة نواهٍ، خلف كلٍّ منها قياس لا تفضيل. وأوّلها هو الأغلى ثمنًا إن اكتُشف متأخّرًا: لا تضع أبدًا مُحرِّكَي حركة على إزاحة تمرير واحدة. وموقع abbod.de القديم فعل ذلك، والمواصفة المستخرَجة بالهندسة العكسية للوحدة الخارجية التي شحنها تستحقّ القراءة بالضبط لأن تلك الوحدة كانت كفؤة.
فقد استدعت preventDefault وstopPropagation، وكتبت الهاش بـ pushState، وقاست الترويسة الثابتة وأزاحت لأجلها، ونقلت التركيز بـ preventScroll، وأعادت tabindex بعدها. فعلت الأشياء الأربعة كلّها التي تقول هذه المقالة إن المُعالِج مدين لك بها. وما لم تستطعه هو أن تعرف بحلقة الحركة الأخرى التي تكتب موضع التمرير نفسه على الصفحة نفسها، عبر 689 مِرساة هاش.
والنهي الثالث عن الأدوات. فهذا الموقع ينشر رقم سهولة الوصول عنده بدل ادّعاء رقم نظيف: axe-core 4.11.0 على 72 وثيقة بعرضين، 144 فحصًا، خمس مخالفات، كلّها قيم لون على عنصر واحد. ولم يكن بوسع أيٍّ منها أن تكون مِرساةً تُحرّك الصفحة ولا تُحرّك التركيز، وسجلّ القياس يقول السبب في قائمته لما لم يُغطَّ — الجولة بلوحة المفاتيح، لأن axe لا يستطيع الضغط على Tab. فالرابط الذي يترك مستعمل لوحة المفاتيح وراءه يجتاز كل فاحص آلي موجود.
والرابع عن مقدار ما ينبغي أن تبنيه من هذا كلّه أصلًا. فنظام التمرير الفرعي هنا مسجَّل مهمًّا لا جوهريًّا أبدًا، وترويسة وحدة المِرساة تنصّ على الاختبار: احذف هذه الوحدة وستظل كل مِرساة على الموقع تعمل. وما تضيفه إلى نقرة الجزء ينبغي أن يكون تحسينًا فوق بدائيّةٍ تعمل أصلًا، لأن البدائيّة هي الشيء الوحيد الذي لا تستطيع جعله أصحّ — لا تستطيع إلا أن تجعله يبدو مختلفًا، وأن تكلّف نفسك أربعة سلوكيات في سبيل ذلك.
المخالفات الخمس الباقية في تلك الجولة، والـ 707 التي تبيّن أنها أثر جانبي للقياس أثناء الحركة، مشروحة في إيجابيات التباين الكاذبة في axe.
والسلوك السليم بلوحة المفاتيح واحد من أربعة أشياء يعدّها هذا الاستوديو جزءًا من البناء لا تدقيقًا بعده — انظر التخصّصات الأربعة.