Το σενάριο είναι σχεδόν πάντα το ίδιο, που είναι ένας πίνακας δεδομένων μέσα σε ένα κοντέινερ με δυνατότητα κύλισης. Κάθε σειρά έχει ένα μενού ενεργειών, ένα μικρό αναπτυσσόμενο μενού με ορισμένες επιλογές, όπως Επεξεργασία, Διπλότυπο και Διαγραφή. Το χτίζεις, φαίνεται να λειτουργεί τέλεια μεμονωμένα, και μετά κάποιος το βάζει μέσα σε αυτό το κυλιόμενο div και τα πράγματα καταρρέουν. Έχω δει αυτό ακριβώς το σφάλμα σε τρεις διαφορετικές βάσεις κωδικών: το κοντέινερ, τη στοίβα και το πλαίσιο, όλα διαφορετικά. Το σφάλμα, όμως, είναι εντελώς πανομοιότυπο.
Το αναπτυσσόμενο μενού κόβεται στην άκρη του κοντέινερ. Ή εμφανίζεται πίσω από περιεχόμενο που λογικά θα έπρεπε να βρίσκεται κάτω από αυτό. Ή λειτουργεί καλά έως ότου ο χρήστης κάνει κύλιση και μετά μετακινείται.
Φτάνετε στο z-index: 9999. Μερικές φορές βοηθά, αλλά άλλες φορές δεν κάνει απολύτως τίποτα. Αυτή η ασυνέπεια είναι η πρώτη ένδειξη ότι κάτι βαθύτερο συμβαίνει.
Ο λόγος που επιστρέφει συνεχώς είναι ότι εμπλέκονται τρία διαφορετικά συστήματα προγράμματος περιήγησης και οι περισσότεροι προγραμματιστές καταλαβαίνουν το καθένα από μόνο του, αλλά ποτέ δεν σκέφτονται τι συμβαίνει όταν και τα τρία συγκρούονται: υπερχείλιση, στοίβαξη πλαισίων και περιορισμός μπλοκ.
Μόλις καταλάβετε πώς αλληλεπιδρούν και οι τρεις, οι λειτουργίες αποτυχίας παύουν να αισθάνονται τυχαίες. Στην πραγματικότητα, γίνονται προβλέψιμα.
Τα Τρία Πράγματα που Πράγματι Προκαλούν Αυτό
Ας δούμε αναλυτικά καθένα από αυτά τα στοιχεία.
Το πρόβλημα υπερχείλισης
Όταν ορίζετε υπερχείλιση: κρυφή, υπερχείλιση: κύλιση ή υπερχείλιση: αυτόματη σε ένα στοιχείο, το πρόγραμμα περιήγησης θα αποκόψει οτιδήποτε εκτείνεται πέρα από τα όριά του, συμπεριλαμβανομένων των απολύτως τοποθετημένων απογόνων.
.scroll-container {
υπερχείλιση: αυτόματη;
ύψος: 300px;
/* Αυτό θα κόψει το αναπτυσσόμενο μενού, τελεία */
}
.αναπτυσσόμενο {
θέση: απόλυτη;
/* Δεν πειράζει -- εξακολουθεί να περικόπτεται από το .scroll-container */
}
Αυτό με εξέπληξε την πρώτη φορά που το αντιμετώπισα. Είχα πάρει τη θέση: το απόλυτο θα άφηνε ένα στοιχείο να ξεφύγει από το απόκομμα ενός κοντέινερ. Δεν το κάνει.
Στην πράξη, αυτό σημαίνει ότι ένα απολύτως τοποθετημένο μενού μπορεί να αποκοπεί από οποιονδήποτε πρόγονο έχει μη ορατή τιμή υπερχείλισης, ακόμα κι αν αυτός ο πρόγονος δεν είναι το μπλοκ που περιέχει το μενού. Το ψαλίδισμα και η τοποθέτηση είναι ξεχωριστά συστήματα. Απλώς τυχαίνει να συγκρούονται με τρόπους που φαίνονται εντελώς τυχαίοι μέχρι να καταλάβετε και τα δύο.
Ακολουθεί ένα παράδειγμα React χρησιμοποιώντας το createPortal:
εισαγωγή {createPortal } από το 'react-dom';
εισαγωγή { useState, useEffect, useRef } από το 'react';
Αναπτυσσόμενη λειτουργία ({ anchorRef, isOpen, παιδιά }) {
const [position, setPosition] = useState({ top: 0, left: 0 });
Και, φυσικά, δεν μπορούμε να αγνοήσουμε την προσβασιμότητα. Τα σταθερά στοιχεία που εμφανίζονται πάνω από το περιεχόμενο πρέπει να εξακολουθούν να είναι προσβάσιμα από το πληκτρολόγιο. Εάν η σειρά εστίασης δεν μετακινείται φυσικά στο σταθερό αναπτυσσόμενο μενού, θα πρέπει να τη διαχειριστείτε χρησιμοποιώντας κώδικα. Αξίζει επίσης να ελέγξετε ότι δεν βρίσκεται πάνω από άλλο διαδραστικό περιεχόμενο χωρίς τρόπο να το απορρίψετε. Αυτό σε δαγκώνει στις δοκιμές πληκτρολογίου.
CSS Anchor Positioning: Πού νομίζω ότι κατευθύνεται
Το CSS Anchor Positioning είναι η κατεύθυνση που με ενδιαφέρει περισσότερο αυτή τη στιγμή. Δεν ήμουν σίγουρος πόσο από τις προδιαγραφές ήταν πραγματικά χρησιμοποιήσιμο όταν το κοίταξα για πρώτη φορά. Σας επιτρέπει να δηλώνετε τη σχέση μεταξύ ενός αναπτυσσόμενου μενού και της ενεργοποίησης του απευθείας στο CSS και το πρόγραμμα περιήγησης χειρίζεται τις συντεταγμένες.
.trigger {
anchor-name: --my-trigger;
}
Η ιδιότητα position-try-fallbacks είναι αυτή που αξίζει να χρησιμοποιηθεί σε έναν χειροκίνητο υπολογισμό. Το πρόγραμμα περιήγησης δοκιμάζει εναλλακτικές τοποθετήσεις πριν εγκαταλείψει, επομένως ένα αναπτυσσόμενο μενού στο κάτω μέρος της θύρας προβολής αναστρέφεται αυτόματα προς τα πάνω αντί να αποκοπεί.
Η υποστήριξη του προγράμματος περιήγησης είναι σταθερή στα προγράμματα περιήγησης που βασίζονται στο Chromium και αυξάνεται στο Safari. Ο Firefox χρειάζεται πολυγέμισμα. Το πακέτο @oddbird/css-anchor-positioning καλύπτει τις βασικές προδιαγραφές. Έχω χτυπήσει θήκες άκρων διάταξης με αυτό που απαιτούσαν εναλλακτικές που δεν περίμενα, επομένως αντιμετωπίστε το ως προοδευτική βελτίωση ή συνδυάστε το με έναΕναλλακτικό JavaScript για Firefox.
Εν ολίγοις, πολλά υποσχόμενο αλλά όχι καθολικό ακόμα. Δοκιμή στα προγράμματα περιήγησης-στόχου σας.
Και όσον αφορά την προσβασιμότητα, η δήλωση μιας οπτικής σχέσης στο CSS δεν λέει τίποτα στο δέντρο προσβασιμότητας. aria-controls, aria-expanded, aria-haspopup — αυτό το μέρος είναι ακόμα σε εσάς.
Μερικές φορές η επιδιόρθωση απλώς μετακινεί το στοιχείο
Πριν φτάσω σε μια πύλη ή κάνω υπολογισμούς συντεταγμένων, ρωτάω πάντα πρώτα μια ερώτηση: Χρειάζεται πράγματι αυτό το αναπτυσσόμενο μενού να βρίσκεται μέσα στο κοντέινερ κύλισης;
Εάν δεν συμβαίνει αυτό, η μετακίνηση της σήμανσης σε ένα περιτύλιγμα υψηλότερου επιπέδου εξαλείφει το πρόβλημα εντελώς, χωρίς JavaScript και υπολογισμούς συντεταγμένων.
Αυτό δεν είναι πάντα δυνατό. Εάν το κουμπί και το αναπτυσσόμενο μενού είναι ενσωματωμένα στο ίδιο στοιχείο, η μετακίνηση του ενός χωρίς το άλλο σημαίνει επανεξέταση ολόκληρου του API. Αλλά όταν μπορείτε να το κάνετε, δεν υπάρχει τίποτα για εντοπισμό σφαλμάτων. Το πρόβλημα απλά δεν υπάρχει.
Τι δεν λύνει ακόμα το σύγχρονο CSS
Το CSS έχει κάνει πολύ δρόμο εδώ, αλλά υπάρχουν ακόμα μέρη που σας απογοητεύουν.
Η θέση: τα ζητήματα που διορθώθηκαν και μετασχηματίστηκαν εξακολουθούν να υπάρχουν. Είναι σκόπιμα στις προδιαγραφές, πράγμα που σημαίνει ότι δεν υπάρχει λύση CSS. Εάν χρησιμοποιείτε μια βιβλιοθήκη κινούμενων εικόνων που αναδιπλώνει τη διάταξή σας σε ένα μετασχηματισμένο στοιχείο, χρειάζεστε ξανά πύλες ή τοποθέτηση αγκύρωσης.
Το CSS Anchor Positioning είναι πολλά υποσχόμενο, αλλά καινούργιο. Όπως αναφέρθηκε προηγουμένως, ο Firefox εξακολουθεί να χρειάζεται πολυγέμισμα τη στιγμή που το γράφω. Έχω χτυπήσει ακραίες περιπτώσεις διάταξης που απαιτούσαν εναλλακτικές που δεν περίμενα. Εάν χρειάζεστε συνεπή συμπεριφορά σε όλα τα προγράμματα περιήγησης σήμερα, εξακολουθείτε να αναζητάτε JavaScript για τα δύσκολα μέρη.
Η προσθήκη για την οποία πραγματικά άλλαξα τη ροή εργασίας μου είναι το HTML Popover API, το οποίο είναι πλέον διαθέσιμο σε όλα τα σύγχρονα προγράμματα περιήγησης. Στοιχεία με το χαρακτηριστικό popover αποδίδονται στο ανώτερο επίπεδο του προγράμματος περιήγησης, πάνω από όλα, χωρίς να απαιτείται τοποθέτηση JavaScript.
Περιεχόμενο Popover
Ο χειρισμός διαφυγής, η απόρριψη-on-click-outside και η σταθερή σημασιολογία προσβασιμότητας παρέχονται δωρεάν για πράγματα όπως συμβουλές εργαλείων, γραφικά στοιχεία αποκάλυψης και απλές επικαλύψεις. Είναι το πρώτο εργαλείο που φτάνω προς το παρόν.
Τούτου λεχθέντος, δεν λύνει την τοποθέτηση. Λύνει το layering. Χρειάζεστε ακόμα τοποθέτηση αγκύρωσης ή JavaScript για να ευθυγραμμίσετε ένα popover με το έναυσμά του. Το Popover API χειρίζεται το layering. Η τοποθέτηση άγκυρας χειρίζεται την τοποθέτηση. Όταν χρησιμοποιούνται μαζί, καλύπτουν το μεγαλύτερο μέρος των όσων θα έπρεπε προηγουμένως να κάνετε για μια βιβλιοθήκη.
Ένας οδηγός απόφασης για την κατάστασή σας
Αφού πέρασα όλα αυτά με τον δύσκολο τρόπο, να πώς σκέφτομαι πραγματικά την επιλογή τώρα.
Χρησιμοποιήστε μια πύλη. Θα το χρησιμοποιούσα όταν η σκανδάλη βρίσκεται βαθιά σε ένθετα δοχεία κύλισης. Χρησιμοποίησα αυτό το μοτίβο για μενού ενεργειών πίνακα και το συνδύασα με επαναφορά εστίασης και ελέγχους προσβασιμότητας. Είναι η πιο αξιόπιστη επιλογή, αλλά προϋπολογισμός χρόνου για την επιπλέον καλωδίωση.
Χρησιμοποιήστε σταθερή τοποθέτηση. Αυτό ισχύει για όταν χρησιμοποιείτε JavaScript βανίλια ή ένα ελαφρύ πλαίσιο και μπορείτε να επαληθεύσετε ότι κανένας πρόγονος δεν εφαρμόζει μετασχηματισμούς ή φίλτρα. Είναι απλό στη ρύθμιση και απλό στον εντοπισμό σφαλμάτων, αρκεί να ισχύει αυτός ο περιορισμός.
Χρησιμοποιήστε το CSS Anchor Positioning. Προσεγγίστε αυτό όταν το επιτρέπει η υποστήριξη του προγράμματος περιήγησής σας. Εάν απαιτείται υποστήριξη Firefox, αντιστοιχίστε την με το @oddbird polyfill. Αυτό είναι όπου η πλατφόρμα κατευθύνεται τελικά και θα γίνει τελικά η προσέγγισή σας.
Αναδιάρθρωση του DOM. Χρησιμοποιήστε το όταν το επιτρέπει η αρχιτεκτονική και θέλετε μηδενική πολυπλοκότητα χρόνου εκτέλεσης. Πιστεύω ότι είναι πιθανώς η πιο υποτιμημένη επιλογή.
Συνδυάστε μοτίβα. Κάντε αυτό όταν θέλετε η τοποθέτηση αγκύρωσης ως κύρια προσέγγιση, σε συνδυασμό με μια εναλλακτική JavaScript για μη υποστηριζόμενα προγράμματα περιήγησης. Ή μια πύλη για τοποθέτηση DOM σε σύζευξη με την getBoundingClientRect() για ακρίβεια συντεταγμένων.
Συμπέρασμα
Αντιμετωπιζόμουν αυτό το σφάλμα ως μεμονωμένο πρόβλημα - κάτι για να επιδιορθωθώ και να προχωρήσω. Αλλά από τη στιγμή που κάθισα μαζί του αρκετά για να καταλάβω και τα τρία εμπλεκόμενα συστήματα - απόκομμα υπερχείλισης, στοίβαξη πλαισίων και περιορισμό μπλοκ - έπαψε να αισθάνεται τυχαίο. Θα μπορούσα να κοιτάξω ένα σπασμένο αναπτυσσόμενο μενού και να εντοπίσω αμέσως ποιος πρόγονος ήταν υπεύθυνος. Αυτή η αλλαγή στον τρόπο με τον οποίο διάβασα το DOM ήταν η πραγματική λήψη.
Δεν υπάρχει ενιαία σωστή απάντηση. Αυτό που έφτασα εξαρτιόταν από το τι μπορούσα να ελέγξω στη βάση κώδικα: πύλες όταν το δέντρο προγονικό ήταν απρόβλεπτο. σταθερή τοποθέτηση όταν ήταν καθαρό και απλό. Μετακίνηση του στοιχείου όταν τίποτα δεν με σταματούσε. και τοποθέτηση αγκύρωσης τώρα,όπου μπορώ.
Ό,τι κι αν επιλέξετε τελικά, μην αντιμετωπίζετε την προσβασιμότητα ως το τελευταίο βήμα. Από την εμπειρία μου, αυτή ακριβώς είναι η στιγμή που παραλείπεται. Οι σχέσεις ARIA, η διαχείριση της εστίασης, η συμπεριφορά του πληκτρολογίου - αυτά δεν είναι πολυτελή. Αποτελούν μέρος αυτού που κάνει το πράγμα να λειτουργεί πραγματικά.
Δείτε τον πλήρη πηγαίο κώδικα στο αποθετήριο GitHub μου.
Περαιτέρω ανάγνωση
Αυτές είναι οι αναφορές στις οποίες επανέρχομαι κατά τη διάρκεια αυτής της διαδικασίας:
Το πλαίσιο στοίβαξης (MDN)
“CSS Anchor Positioning Guide”, Juan Diego Rodriguez
“Getting Started With The Popover API”, Godstime Aburu
Κυμαινόμενο περιβάλλον χρήστη (floating-ui.com)
Υπερχείλιση CSS (MDN)