Al momento, come colleghi un elemento a un altro? Puoi provare a monitorare le relative posizioni o utilizzare una qualche forma di elemento wrapper.
<!-- index.html -->
<div class="container">
<a href="/link" class="anchor">I’m the anchor</a>
<div class="anchored">I’m the anchored thing</div>
</div>
/* styles.css */
.container {
position: relative;
}
.anchored {
position: absolute;
}
Spesso queste soluzioni non sono ideali. Hanno bisogno di JavaScript o introducono markup aggiuntivo. L'API CSS Anchor Positioning mira a risolvere questo problema fornendo un'API CSS per il tethering degli elementi. Fornisce un mezzo per posizionare e dimensionare un elemento in base alla posizione e alle dimensioni di altri elementi.
Supporto browser
Puoi provare l'API di posizionamento dell'ancora CSS in Chrome Canary dietro il flag "Funzionalità sperimentali della piattaforma web". Per attivare questo flag, apri Chrome Canary e visita la pagina chrome://flags
. Poi attiva il flag "Funzionalità sperimentali della piattaforma web".
Inoltre, il team di Oddbird sta sviluppando un polyfill. Assicurati di controllare il repository all'indirizzo github.com/oddbird/css-anchor-positioning.
Puoi verificare il supporto dell'ancoraggio con:
@supports(anchor-name: --foo) {
/* Styles... */
}
Tieni presente che questa API è ancora in fase sperimentale e potrebbe cambiare. Questo articolo illustra a grandi linee le parti importanti. Inoltre, l'implementazione attuale non è completamente in sincronizzazione con la specifica del gruppo di lavoro CSS.
Il problema
Perché eseguire questa operazione? Un caso d'uso importante è la creazione di descrizioni comando o esperienze simili. In questo caso, spesso è opportuno collegare la descrizione comando ai contenuti a cui fa riferimento. Spesso è necessario un modo per collegare un elemento a un altro. Inoltre, ti aspetti che l'interazione con la pagina non rompa il legame, ad esempio se un utente scorre o ridimensiona l'interfaccia utente.
Un altro aspetto del problema è che, se vuoi assicurarti che l'elemento collegato rimanga visibile, ad esempio se apri una descrizione comando e questa viene tagliata dai limiti dell'area visibile, Questa potrebbe non essere un'esperienza ottimale per gli utenti. Vuoi che la descrizione comando si adatti.
Soluzioni attuali
Al momento, esistono diversi modi per affrontare il problema.
Il primo è l'approccio rudimentale "Inserisci un a capo nell'ancora". Prendi entrambi gli elementi e li inserisci in un contenitore. Poi puoi utilizzare position
per posizionare la descrizione comando rispetto all'ancora.
<div class="containing-block">
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
</div>
.containing-block {
position: relative;
}
.tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
}
Puoi spostare il contenitore e tutto rimarrà dove vuoi per la maggior parte.
Un altro approccio potrebbe essere quello di conoscere la posizione della tua ancora o di poterla monitorare in qualche modo. Puoi passarlo alla descrizione comando con proprietà personalizzate.
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
:root {
--anchor-width: 120px;
--anchor-top: 40vh;
--anchor-left: 20vmin;
}
.anchor {
position: absolute;
top: var(--anchor-top);
left: var(--anchor-left);
width: var(--anchor-width);
}
.tooltip {
position: absolute;
top: calc(var(--anchor-top));
left: calc((var(--anchor-width) * 0.5) + var(--anchor-left));
transform: translate(-50%, calc(-100% - 10px));
}
Ma cosa succede se non conosci la posizione dell'ancora? È probabile che tu debba intervenire con JavaScript. Potresti fare qualcosa di simile al codice seguente, ma ora significa che i tuoi stili stanno iniziando a fuoriuscire dal CSS e a passare a JavaScript.
const setAnchorPosition = (anchored, anchor) => {
const bounds = anchor.getBoundingClientRect().toJSON();
for (const [key, value] of Object.entries(bounds)) {
anchored.style.setProperty(`--${key}`, value);
}
};
const update = () => {
setAnchorPosition(
document.querySelector('.tooltip'),
document.querySelector('.anchor')
);
};
window.addEventListener('resize', update);
document.addEventListener('DOMContentLoaded', update);
A questo punto sorgono alcune domande:
- Quando calcolo gli stili?
- Come faccio a calcolare gli stili?
- Con quale frequenza calcolo gli stili?
Il problema è stato risolto? Potrebbe essere adatta al tuo caso d'uso, ma c'è un problema: la nostra soluzione non si adatta. Non è reattivo. Cosa succede se l'elemento ancorato viene tagliato dal viewport?
Ora devi decidere se reagire e come. Il numero di domande e decisioni che devi prendere sta iniziando ad aumentare. Vuoi solo ancorare un elemento a un altro. In un mondo ideale, la soluzione si adeguerà e reagirà all'ambiente circostante.
Per ovviare a questo problema, puoi utilizzare una soluzione JavaScript. Ciò comporterà il costo dell'aggiunta di una dipendenza al progetto e potrebbe causare problemi di prestazioni a seconda di come li utilizzi. Ad esempio, alcuni pacchetti utilizzano requestAnimationFrame
per mantenere la posizione corretta. Ciò significa che tu e il tuo team dovete acquisire familiarità con il pacchetto e le relative opzioni di configurazione. Di conseguenza, le tue domande e le tue decisioni potrebbero non essere ridotte, ma modificate. Questo è il motivo del posizionamento dell'ancora CSS. In questo modo non dovrai preoccuparti di problemi di prestazioni durante il calcolo della posizione.
Ecco un esempio di codice per l'utilizzo di "floating-ui", un pacchetto molto utilizzato per questo problema:
import {computePosition, flip, offset, autoUpdate} from 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.1/+esm';
const anchor = document.querySelector('.anchor')
const tooltip = document.querySelector('.tooltip')
const updatePosition = () => {
computePosition(anchor, tooltip, {
placement: 'top',
middleware: [offset(10), flip()]
})
.then(({x, y}) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`
})
})
};
const clean = autoUpdate(anchor, tooltip, updatePosition);
Prova a riposizionare l'ancora in questa demo che utilizza il codice.
La "mostra testo" potrebbe non comportarsi come previsto. Reagisce all'uscita dall'area visibile sull'asse Y, ma non sull'asse X. Consulta la documentazione e probabilmente troverai una soluzione adatta a te.
Tuttavia, trovare un pacchetto adatto al tuo progetto può richiedere molto tempo. Si tratta di decisioni aggiuntive e può essere frustrante se non funzionano come vuoi.
Utilizzare il posizionamento dell'ancora
Inserisci l'API di posizionamento dell'ancora CSS. L'idea è mantenere gli stili nel CSS e ridurre il numero di decisioni da prendere. Speri di ottenere lo stesso risultato, ma l'obiettivo è migliorare l'esperienza degli sviluppatori.
- Non è richiesto JavaScript.
- Lascia che sia il browser a trovare la posizione migliore in base alle tue indicazioni.
- Nessuna dipendenza da terze parti
- Nessun elemento wrapper.
- Funziona con gli elementi nel livello superiore.
Ricreiamo e affrontiamo il problema che stavamo cercando di risolvere sopra. Utilizza invece l'analogia di una barca con un'ancora. Questi rappresentano l'elemento ancorato e l'ancora. L'acqua rappresenta il blocco contenente.
Innanzitutto, devi scegliere come definire l'ancora. Puoi farlo nel CSS impostando la proprietà anchor-name
sull'elemento anchor. Accetta un valore dashed-ident.
.anchor {
anchor-name: --my-anchor;
}
In alternativa, potrai definire un'ancora nel codice HTML con l'attributo anchor
. Il valore dell'attributo è l'ID dell'elemento di ancoraggio. Viene creata un'ancora implicita.
<a id="my-anchor" class="anchor"></a>
<div anchor="my-anchor" class="boat">I’m a boat!</div>
Una volta definita un'ancora, puoi utilizzare la funzione anchor
. La funzione anchor
accetta tre argomenti:
- Elemento di ancoraggio: il
anchor-name
dell'ancora da utilizzare oppure puoi omettere il valore per utilizzare un'ancoraimplicit
. Può essere definito tramite la relazione HTML o con una proprietàanchor-default
con un valoreanchor-name
. - Lato di ancoraggio:una parola chiave della posizione che vuoi utilizzare. Può essere
top
,right
,bottom
,left
,center
e così via. In alternativa, puoi passare una percentuale. Ad esempio, il 50% corrisponde acenter
. - Valore alternativo:si tratta di un valore alternativo facoltativo che accetta una durata o una percentuale.
La funzione anchor
viene utilizzata come valore per le proprietà di incasso (top
, right
, bottom
, left
o i relativi equivalenti logici) dell'elemento ancorato. Puoi utilizzare la funzione anchor
anche in calc
:
.boat {
bottom: anchor(--my-anchor top);
left: calc(anchor(--my-anchor center) - (var(--boat-size) * 0.5));
}
/* alternative with anchor-default */
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: calc(anchor(center) - (var(--boat-size) * 0.5));
}
Non esiste una proprietà di rientranza center
, quindi un'opzione è utilizzare calc
se conosci le dimensioni dell'elemento ancorato. Perché non utilizzare translate
? Potresti utilizzare:
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: anchor(center);
translate: -50% 0;
}
Tuttavia, il browser non prende in considerazione le posizioni trasformate per gli elementi ancorati. Sarà chiaro perché questo è importante quando si prendono in considerazione le alternative alla posizione e il posizionamento automatico.
Potresti aver notato l'utilizzo della proprietà personalizzata --boat-size
sopra. Tuttavia, se vuoi basare le dimensioni dell'elemento ancorato su quelle dell'ancora, puoi accedere anche a queste dimensioni. Invece di calcolarlo manualmente, puoi utilizzare la funzione anchor-size
. Ad esempio, per fare in modo che la barca sia quattro volte più larga dell'ancora:
.boat {
width: calc(4 * anchor-size(--my-anchor width));
}
Con anchor-size(--my-anchor height)
hai accesso anche all'altezza. Puoi utilizzarlo per impostare le dimensioni di uno o di entrambi gli assi.
Che cosa succede se vuoi ancorare un elemento con il posizionamento absolute
? La regola è che gli elementi non possono essere fratelli. In questo caso, puoi racchiudere l'ancora con un contenitore con posizionamento relative
. Dopodiché puoi ancorarlo.
<div class="anchor-wrapper">
<a id="my-anchor" class="anchor"></a>
</div>
<div class="boat">I’m a boat!</div>
Dai un'occhiata a questa demo in cui puoi trascinare l'ancora e la barca la seguirà.
Monitoraggio della posizione di scorrimento
In alcuni casi, l'elemento di ancoraggio potrebbe trovarsi all'interno di un contenitore a scorrimento. Allo stesso tempo, l'elemento ancorato potrebbe trovarsi all'esterno del contenitore. Poiché lo scorrimento avviene in un thread diverso dal layout, devi trovare un modo per monitorarlo. La proprietà anchor-scroll
può eseguire questa operazione. Lo imposti sull'elemento ancorato e gli assegni il valore dell'ancora che vuoi monitorare.
.boat { anchor-scroll: --my-anchor; }
Prova questa demo in cui puoi attivare e disattivare anchor-scroll
con la casella di controllo nell'angolo.
L'analogia non è del tutto calzante, perché in un mondo ideale la barca e l'ancora sono entrambe in acqua. Inoltre, funzionalità come l'API Popover consentono di tenere vicini gli elementi correlati. Il posizionamento dell'ancora funzionerà però con gli elementi nel livello superiore. Questo è uno dei principali vantaggi dell'API: la possibilità di collegare elementi in flussi diversi.
Considera questa demo che ha un contenitore scorrevole con ancore con descrizioni comando. Gli elementi della descrizione comando che sono popup potrebbero non essere nello stesso spazio delle ancore:
Tuttavia, noterai che i popup monitorano i rispettivi link di ancoraggio. Puoi ridimensionare il contenitore con scorrimento e le posizioni verranno aggiornate automaticamente.
Posizione di riserva e posizionamento automatico
È qui che il potere del posizionamento dell'ancora sale di un livello. Un position-fallback
può posizionare l'elemento ancorato in base a un insieme di valori di riserva che fornisci. Indirizzi il browser con i tuoi stili e lasci che calcoli la posizione per te.
Il caso d'uso comune è una descrizione comando che deve essere visualizzata sopra o sotto un'ancora. Questo comportamento si basa sul fatto che la descrizione comando venga ritagliata dal relativo contenitore. Di solito, questo contenitore è l'area visibile.
Se hai esaminato il codice dell'ultima demo, avrai notato che era in uso una proprietà position-fallback
. Se hai fatto scorrere il contenitore, potresti aver notato che i popup ancorati sono saltati. Questo accadeva quando le rispettive ancore si avvicinavano al confine dell'area visibile. A quel punto, i popup cercano di adattarsi per rimanere nel viewport.
Prima di creare un position-fallback
esplicito, il posizionamento dell'ancora offrirà anche il posizionamento automatico. Puoi ottenere questa rotazione senza costi utilizzando un valore di auto
sia nella funzione di ancoraggio sia nella proprietà di incavo opposta. Ad esempio, se utilizzi anchor
per bottom
, imposta top
su auto
.
.tooltip {
position: absolute;
bottom: anchor(--my-anchor auto);
top: auto;
}
L'alternativa al posizionamento automatico è utilizzare un position-fallback
esplicito. Per farlo, devi definire un insieme di valori predefiniti per la posizione. Il browser li esaminerà finché non ne troverà uno che può utilizzare e poi applicherà quel posizionamento. Se non riesce a trovarne uno funzionante, viene utilizzato per impostazione predefinita il primo definito.
Un position-fallback
che tenta di visualizzare le descrizioni comando sopra e sotto potrebbe avere il seguente aspetto:
@position-fallback --top-to-bottom {
@try {
bottom: anchor(top);
left: anchor(center);
}
@try {
top: anchor(bottom);
left: anchor(center);
}
}
Se lo applichi alle descrizioni comando, il risultato sarà il seguente:
.tooltip {
anchor-default: --my-anchor;
position-fallback: --top-to-bottom;
}
L'utilizzo di anchor-default
significa che puoi riutilizzare position-fallback
per altri elementi. Puoi anche utilizzare una proprietà personalizzata basata sugli ambiti per impostare anchor-default
.
Guarda di nuovo questa demo con la barca. È impostato un position-fallback
. Man mano che modifichi la posizione dell'ancora, la barca si regola per rimanere all'interno del contenitore. Prova a modificare anche il valore di spaziatura, che regola la spaziatura del corpo. Nota come il browser corregge il posizionamento. Le posizioni vengono modificate cambiando l'allineamento della griglia del contenitore.
Questa volta position-fallback
è più dettagliato e prova le posizioni in senso orario.
.boat {
anchor-default: --my-anchor;
position-fallback: --compass;
}
@position-fallback --compass {
@try {
bottom: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
right: anchor(left);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
}
Esempi
Ora che hai un'idea delle funzionalità principali per il posizionamento degli elementi di ancoraggio, diamo un'occhiata ad alcuni esempi interessanti oltre alle descrizioni comando. Questi esempi hanno lo scopo di farti venire in mente dei modi in cui puoi utilizzare il posizionamento dell'ancora. Il modo migliore per migliorare le specifiche è ricevere il feedback di utenti reali come te.
Menu contestuali
Iniziamo con un menu contestuale che utilizza l'API Popover. L'idea è che facendo clic sul pulsante con il triangolo, venga visualizzato un menu contestuale. Questo menu avrà un proprio menu da espandere.
Il markup non è la parte importante. Tuttavia, hai tre pulsanti che utilizzano ciascuno popovertarget
. Poi ci sono tre elementi che utilizzano l'attributo popover
. In questo modo puoi aprire i menu contestuali senza JavaScript. Potrebbe avere il seguente aspetto:
<button popovertarget="context">
Toggle Menu
</button>
<div popover="auto" id="context">
<ul>
<li><button>Save to your Liked Songs</button></li>
<li>
<button popovertarget="playlist">
Add to Playlist
</button>
</li>
<li>
<button popovertarget="share">
Share
</button>
</li>
</ul>
</div>
<div popover="auto" id="share">...</div>
<div popover="auto" id="playlist">...</div>
Ora puoi definire un position-fallback
e condividerlo tra i menu contestuali. Assicuriamo inoltre di annullare l'impostazione di eventuali stili inset
per i popup.
[popovertarget="share"] {
anchor-name: --share;
}
[popovertarget="playlist"] {
anchor-name: --playlist;
}
[popovertarget="context"] {
anchor-name: --context;
}
#share {
anchor-default: --share;
position-fallback: --aligned;
}
#playlist {
anchor-default: --playlist;
position-fallback: --aligned;
}
#context {
anchor-default: --context;
position-fallback: --flip;
}
@position-fallback --aligned {
@try {
top: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
@try {
top: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(bottom);
left: anchor(right);
}
@try {
right: anchor(left);
bottom: anchor(bottom);
}
}
@position-fallback --flip {
@try {
bottom: anchor(top);
left: anchor(left);
}
@try {
right: anchor(right);
bottom: anchor(top);
}
@try {
top: anchor(bottom);
left: anchor(left);
}
@try {
top: anchor(bottom);
right: anchor(right);
}
}
In questo modo, avrai a disposizione un'interfaccia utente del menu contestuale nidificato adattabile. Prova a modificare la posizione dei contenuti con la selezione. L'opzione scelta aggiorna l'allineamento della griglia. Ciò influisce sul posizionamento dei popup in base al posizionamento dell'ancora.
Messa a fuoco e monitoraggio
Questa demo combina le primitive CSS inserendo :has(). L'idea è eseguire la transizione di un indicatore visivo per l'input
che ha il focus.
Per farlo, imposta un nuovo ancoraggio in fase di esecuzione. Per questa demo, una proprietà personalizzata basata sugli ambiti viene aggiornata quando l'input è attivo.
#email {
anchor-name: --email;
}
#name {
anchor-name: --name;
}
#password {
anchor-name: --password;
}
:root:has(#email:focus) {
--active-anchor: --email;
}
:root:has(#name:focus) {
--active-anchor: --name;
}
:root:has(#password:focus) {
--active-anchor: --password;
}
:root {
--active-anchor: --name;
--active-left: anchor(var(--active-anchor) right);
--active-top: calc(
anchor(var(--active-anchor) top) +
(
(
anchor(var(--active-anchor) bottom) -
anchor(var(--active-anchor) top)
) * 0.5
)
);
}
.form-indicator {
left: var(--active-left);
top: var(--active-top);
transition: all 0.2s;
}
Ma come puoi fare un ulteriore passo avanti? Potresti utilizzarlo per una sorta di overlay didattico. Una descrizione comando potrebbe spostarsi tra i punti d'interesse e aggiornarne i contenuti. Puoi usare la transizione sfumata tra i contenuti. In questo caso potrebbero essere utili animazioni discrete che ti consentano di animare display
o le transizioni di visualizzazione.
Calcolo grafico a barre
Un'altra cosa divertente che puoi fare con il posizionamento dell'ancora è combinarlo con calc
. Immagina un grafico con alcuni popup che lo annotano.
Puoi monitorare i valori più alti e più bassi utilizzando min
e max
CSS. Il CSS potrebbe avere il seguente aspetto:
.chart__tooltip--max {
left: anchor(--chart right);
bottom: max(
anchor(--anchor-1 top),
anchor(--anchor-2 top),
anchor(--anchor-3 top)
);
translate: 0 50%;
}
Viene utilizzato del codice JavaScript per aggiornare i valori del grafico e del CSS per impostarne lo stile. Tuttavia, il posizionamento dell'ancora si occupa degli aggiornamenti del layout per noi.
Maniglie di ridimensionamento
Non è necessario ancorare solo a un elemento. Puoi utilizzare più ancore per un elemento. Probabilmente l'hai notato nell'esempio del grafico a barre. Le descrizioni comando sono state ancorate al grafico e poi alla barra appropriata. Se approfondisci questo concetto, puoi utilizzarlo per ridimensionare gli elementi.
Puoi trattare i punti di ancoraggio come maniglie di ridimensionamento personalizzate e utilizzare un valore inset
.
.container {
position: absolute;
inset:
anchor(--handle-1 top)
anchor(--handle-2 right)
anchor(--handle-2 bottom)
anchor(--handle-1 left);
}
In questa demo, GreenSock Draggable rende spostabili le maniglie. Tuttavia, l'elemento <img>
viene ridimensionato per riempire il contenitore che si regola per colmare lo spazio tra i manici.
Un SelectMenu?
L'ultima è un po' un'anticipazione di ciò che verrà. Tuttavia, puoi creare un popup attivabile e ora hai il posizionamento dell'ancora. Potresti creare le basi di un elemento <select>
personalizzabile.
<div class="select-menu">
<button popovertarget="listbox">
Select option
<svg>...</svg>
</button>
<div popover="auto" id="listbox">
<option>A</option>
<option>Styled</option>
<option>Select</option>
</div>
</div>
Un anchor
implicito semplifica la procedura. Tuttavia, il CSS per un punto di partenza rudimentale potrebbe avere il seguente aspetto:
[popovertarget] {
anchor-name: --select-button;
}
[popover] {
anchor-default: --select-button;
top: anchor(bottom);
width: anchor-size(width);
left: anchor(left);
}
Combina le funzionalità dell'API Popover con il posizionamento dell'ancora CSS e hai quasi finito.
È fantastico quando inizi a introdurre elementi come :has()
. Puoi ruotare l'indicatore quando è aperto:
.select-menu:has(:open) svg {
rotate: 180deg;
}
Dove potresti portarlo? Cosa ci serve per rendere funzionale select
? Ne parleremo nel prossimo articolo. Ma non preoccuparti, a breve saranno disponibili elementi di selezione personalizzabili. Continua a seguirci.
È tutto!
La piattaforma web è in evoluzione. Il posizionamento dell'ancora CSS è un elemento fondamentale per migliorare il modo in cui sviluppi i controlli dell'interfaccia utente. Ti consentirà di evitare alcune decisioni complicate. Ma ti consentirà anche di fare cose che non avresti mai potuto fare prima. Ad esempio, puoi applicare uno stile a un elemento <select>
. Facci conoscere le tue impressioni.
Foto di CHUTTERSNAP su Unsplash