Questo articolo descrive perché e come abbiamo implementato la simulazione della carenza di visione dei colori in DevTools e nel renderer Blink.
Sfondo: contrasto di colore scadente
Il testo a basso contrasto è il problema di accessibilità più comune rilevabile automaticamente sul web.
Secondo l'analisi dell'accessibilità di WebAIM relativa ai principali 1 milione di siti web, oltre l'86% delle home page ha un basso contrasto. In media, ogni home page contiene 36 istanze distinte di testo a basso contrasto.
Utilizzare DevTools per trovare, comprendere e risolvere i problemi di contrasto
Chrome DevTools può aiutare sviluppatori e designer a migliorare il contrasto e a scegliere combinazioni di colori più accessibili per le app web:
- La descrizione comando della modalità di ispezione visualizzata nella parte superiore della pagina web mostra il rapporto di contrasto per gli elementi di testo.
- Il selettore di colori di DevTools evidenzia i rapporti di contrasto non corretti per gli elementi di testo, mostra la linea di contrasto consigliata per aiutarti a selezionare manualmente colori migliori e può persino suggerire colori accessibili.
- Sia il pannello Panoramica CSS sia il report di controllo dell'accessibilità di Lighthouse elencano gli elementi di testo a basso contrasto rilevati nella tua pagina.
Di recente abbiamo aggiunto un nuovo strumento a questo elenco, che è un po' diverso dagli altri. Gli strumenti descritti sopra si concentrano principalmente sulla visualizzazione delle informazioni sul rapporto di contrasto e sulla fornitura di opzioni per correggerlo. Ci siamo resi conto che in DevTools mancava ancora un modo per consentire agli sviluppatori di comprendere più a fondo questo spazio di problemi. Per risolvere il problema, abbiamo implementato la simulazione di difetti alla vista nella scheda Rendering di DevTools.
In Puppeteer, la nuova API page.emulateVisionDeficiency(type)
ti consente di attivare queste simulazioni in modo programmatico.
Carenze di visione dei colori
Circa una persona su 20 soffre di una discromatopsia (nota anche con il termine meno preciso "daltonismo"). Questi disturbi rendono più difficile distinguere i diversi colori, il che può amplificare i problemi di contrasto.
In qualità di sviluppatore con visione regolare, DevTools potrebbe mostrare un rapporto di contrasto errato per le coppie di colori che sembrano corrette. Questo accade perché le formule del rapporto di contrasto prendono in considerazione queste carenze nella visione dei colori. Tu potresti riuscire a leggere il testo a basso contrasto in alcuni casi, ma le persone con disabilità visive non hanno questo privilegio.
Permettendo a designer e sviluppatori di simulare l'effetto di queste carenze visive sulle proprie app web, miriamo a fornire l'elemento mancante: non solo DevTools può aiutarti a individuare e risolvere i problemi di contrasto, ma ora puoi anche comprendere questi problemi.
Simulazione di carenze di visione dei colori con HTML, CSS, SVG e C++
Prima di esaminare l'implementazione della nostra funzionalità in Blink Renderer, è utile capire come implementare una funzionalità equivalente utilizzando la tecnologia web.
Puoi considerare ciascuna di queste simulazioni di deficienza della visione dei colori come un overlay che copre l'intera pagina. La piattaforma web ha un modo per farlo: i filtri CSS. Con la proprietà CSS filter
, puoi utilizzare alcune funzioni di filtro predefinite, come blur
, contrast
, grayscale
, hue-rotate
e molte altre. Per un controllo ancora maggiore, la proprietà filter
accetta anche un URL che può puntare a una definizione di filtro SVG personalizzato:
<style>
:root {
filter: url(#deuteranopia);
}
</style>
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
L'esempio riportato sopra utilizza una definizione di filtro personalizzato basata su una matrice di colori. In linea di principio, il valore di colore [Red, Green, Blue, Alpha]
di ogni pixel viene moltiplicato per una matrice per creare un nuovo colore [R′, G′, B′, A′]
.
Ogni riga della matrice contiene 5 valori: un moltiplicatore per R, G, B e A (da sinistra a destra) e un quinto valore per uno spostamento costante. Esistono 4 righe: la prima riga della matrice viene utilizzata per calcolare il nuovo valore rosso, la seconda riga verde, la terza riga blu e l'ultima riga alfa.
Forse ti starai chiedendo da dove provengono i numeri esatti del nostro esempio. Cosa rende questa matrice di colori una buona approssimazione della deuteranopia? La risposta è: scienza! I valori si basano su un modello di simulazione di deficit di visione dei colori fisiologicamente accurato di Machado, Oliveira e Fernandes.
Ad ogni modo, abbiamo questo filtro SVG e ora possiamo applicarlo a elementi arbitrari della pagina utilizzando il CSS. Possiamo ripetere lo stesso schema per altre carenze di vista. Ecco una demo di come funziona:
Se volessimo, potremmo creare la nostra funzionalità di DevTools nel seguente modo: quando l'utente emula una deficienza visiva nell'interfaccia utente di DevTools, iniettiamo il filtro SVG nel documento ispezionato e poi applichiamo lo stile del filtro all'elemento principale. Tuttavia, questo approccio presenta diversi problemi:
- La pagina potrebbe già avere un filtro sull'elemento principale, che il nostro codice potrebbe quindi sostituire.
- La pagina potrebbe già avere un elemento con
id="deuteranopia"
, in conflitto con la nostra definizione di filtro. - La pagina potrebbe basarsi su una determinata struttura DOM e, inserendo
<svg>
nel DOM, potremmo violare queste ipotesi.
A parte i casi limite, il problema principale di questo approccio è che apporteremo modifiche alla pagina osservabili in modo programmatico. Se un utente di DevTools ispeziona il DOM, potrebbe improvvisamente vedere un elemento <svg>
che non ha mai aggiunto o un filter
CSS che non ha mai scritto. Sarebbe troppo complicato. Per implementare questa funzionalità in DevTools, abbiamo bisogno di una soluzione che non presenti questi svantaggi.
Vediamo come possiamo renderlo meno invadente. Questa soluzione è composta da due parti: 1) lo stile CSS con la proprietà filter
e 2) la definizione del filtro SVG, che attualmente fa parte del DOM.
<!-- Part 1: the CSS style with the filter property -->
<style>
:root {
filter: url(#deuteranopia);
}
</style>
<!-- Part 2: the SVG filter definition -->
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
Evitare la dipendenza SVG all'interno del documento
Iniziamo con la parte 2: come possiamo evitare di aggiungere l'SVG al DOM? Un'idea è spostarlo in un file SVG separato. Possiamo copiare il file <svg>…</svg>
dal codice HTML riportato sopra e salvarlo come filter.svg
, ma prima dobbiamo apportare alcune modifiche. SVG in linea in HTML segue le regole di analisi dell'HTML. Ciò significa che in alcuni casi puoi omettere le virgolette intorno ai valori degli attributi. Tuttavia, i file SVG in file separati dovrebbero essere XML valido e l'analisi XML è molto più rigorosa dell'HTML. Ecco di nuovo il nostro snippet SVG in HTML:
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
Per creare un SVG autonomo valido (e quindi XML), dobbiamo apportare alcune modifiche. Riesci a indovinare quale?
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000" />
</filter>
</svg>
La prima modifica è la dichiarazione dello spazio dei nomi XML in alto. La seconda aggiunta è il cosiddetto "solidus", la barra che indica che il tag <feColorMatrix>
apre e chiude l'elemento. Quest'ultima modifica non è in realtà necessaria (potremmo semplicemente attenerci al tag di chiusura </feColorMatrix>
esplicito), ma poiché sia XML che SVG-in-HTML supportano questa abbreviazione />
, potremmo anche utilizzarla.
Comunque, con queste modifiche, possiamo infine salvarlo come un file SVG valido e puntare a quest'ultimo dal valore della proprietà filter
CSS nel nostro documento HTML:
<style>
:root {
filter: url(filters.svg#deuteranopia);
}
</style>
Evviva, non dobbiamo più iniettare SVG nel documento. Va già molto meglio. Ma… ora abbiamo bisogno di un file separato. Si tratta comunque di una dipendenza. Possiamo in qualche modo eliminarlo?
A quanto pare, non abbiamo bisogno di un file. Possiamo codificare l'intero file all'interno di un URL utilizzando un URL di dati. Per farlo, prendiamo letteralmente i contenuti del file SVG che avevamo prima, aggiungiamo il prefisso data:
, configuriamo il tipo MIME appropriato e otteniamo un URL dati valido che rappresenta lo stesso file SVG:
data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000" />
</filter>
</svg>
Il vantaggio è che ora non dobbiamo più archiviare il file da nessuna parte, né caricarlo dal disco o sulla rete solo per utilizzarlo nel nostro documento HTML. Pertanto, anziché fare riferimento al nome del file come facevamo in precedenza, ora possiamo indicare l'URL dei dati:
<style>
:root {
filter: url('data:image/svg+xml,\
<svg xmlns="http://www.w3.org/2000/svg">\
<filter id="deuteranopia">\
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000\
0.280 0.673 0.047 0.000 0.000\
-0.012 0.043 0.969 0.000 0.000\
0.000 0.000 0.000 1.000 0.000" />\
</filter>\
</svg>#deuteranopia');
}
</style>
Alla fine dell'URL, specifichiamo ancora l'ID del filtro che vogliamo utilizzare, come prima. Tieni presente che non è necessario codificare il documento SVG nell'URL utilizzando la codifica Base64, altrimenti questa operazione potrebbe solo compromettere la leggibilità e aumentare le dimensioni del file. Abbiamo aggiunto barre oblique alla fine di ogni riga per assicurarci che i caratteri di a capo nell'URL dei dati non terminino la stringa letterale CSS.
Finora abbiamo parlato solo di come simulare le deficienze visive utilizzando la tecnologia web. È interessante notare che la nostra implementazione finale in Blink Renderer è in realtà molto simile. Ecco un'utilità di supporto C++ che abbiamo aggiunto per creare un URL dati con una determinata definizione di filtro, in base alla stessa tecnica:
AtomicString CreateFilterDataUrl(const char* piece) {
AtomicString url =
"data:image/svg+xml,"
"<svg xmlns=\"http://www.w3.org/2000/svg\">"
"<filter id=\"f\">" +
StringView(piece) +
"</filter>"
"</svg>"
"#f";
return url;
}
Ed ecco come lo utilizziamo per creare tutti i filtri di cui abbiamo bisogno:
AtomicString CreateVisionDeficiencyFilterUrl(VisionDeficiency vision_deficiency) {
switch (vision_deficiency) {
case VisionDeficiency::kAchromatopsia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kBlurredVision:
return CreateFilterDataUrl("<feGaussianBlur stdDeviation=\"2\"/>");
case VisionDeficiency::kDeuteranopia:
return CreateFilterDataUrl(
"<feColorMatrix values=\""
" 0.367 0.861 -0.228 0.000 0.000 "
" 0.280 0.673 0.047 0.000 0.000 "
"-0.012 0.043 0.969 0.000 0.000 "
" 0.000 0.000 0.000 1.000 0.000 "
"\"/>");
case VisionDeficiency::kProtanopia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kTritanopia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kNoVisionDeficiency:
NOTREACHED();
return "";
}
}
Tieni presente che questa tecnica ci consente di accedere a tutte le potenzialità dei filtri SVG senza dover riimplementare nulla o reinventare la ruota. Stiamo implementando una funzionalità del Renderer Blink, ma lo stiamo facendo sfruttando la piattaforma web.
Bene, abbiamo capito come creare filtri SVG e trasformarli in URL di dati che possiamo utilizzare nel valore della proprietà filter
CSS. Riesci a pensare a un problema con questa tecnica? A quanto pare, non possiamo affidarsi sempre sull'URL di dati caricato, poiché la pagina di destinazione potrebbe avere un Content-Security-Policy
che blocca gli URL di dati. La nostra implementazione finale a livello di Blink presta particolare attenzione a bypassare CSP per questi URL di dati "interni" durante il caricamento.
A parte i casi limite, abbiamo fatto buoni progressi. Poiché non dipendiamo più dalla presenza di <svg>
in linea nello stesso documento, abbiamo ridotto efficacemente la nostra soluzione a una singola definizione di proprietà filter
CSS autonomo. Bene. Ora sbarazzamoci anche di questo.
Evitare la dipendenza da CSS nel documento
Per riepilogare, ecco dove siamo finora:
<style>
:root {
filter: url('data:…');
}
</style>
Dipendiamo ancora da questa proprietà CSS filter
, che potrebbe sostituire un filter
nel documento reale e causare dei problemi. Verrebbe visualizzato anche durante l'ispezione degli stili calcolati in DevTools, il che potrebbe creare confusione. Come possiamo evitare questi problemi? Dobbiamo trovare un modo per aggiungere un filtro al documento senza che sia osservabile dagli sviluppatori tramite programmazione.
Un'idea che è emersa è stata quella di creare una nuova proprietà CSS interna di Chrome che si comporti come filter
, ma abbia un nome diverso, ad esempio --internal-devtools-filter
. Potremmo quindi aggiungere una logica speciale per assicurarci che questa proprietà non venga mai visualizzata in DevTools o negli stili calcolati nel DOM. Potremmo anche assicurarci che funzioni solo sull'elemento di cui abbiamo bisogno: l'elemento principale. Tuttavia, questa soluzione non sarebbe ideale: duplicheremmo la funzionalità già esistente con filter
e, anche se ci sforziamo di nascondere questa proprietà non standard, gli sviluppatori web potrebbero comunque scoprirla e iniziare a utilizzarla, il che sarebbe negativo per la piattaforma web. Abbiamo bisogno di un altro modo per applicare uno stile CSS senza che sia osservabile nel DOM. Qualche idea?
La specifica CSS contiene una sezione che introduce il modello di formattazione visiva utilizzato e uno dei concetti chiave è il viewport. Si tratta della visualizzazione visiva attraverso la quale gli utenti consultano la pagina web. Un concetto strettamente correlato è l'iniziale contenente il blocco, che è un po' come un'area visibile <div>
con stile che esiste solo a livello di specifica. La specifica fa riferimento a questo concetto di "viewport" ovunque. Ad esempio, sai come il browser mostra le barre di scorrimento quando i contenuti non si adattano? Tutto questo è definito nella specifica CSS, in base a questo "viewport".
Questo viewport
esiste anche nel renderer di Blink, come dettaglio di implementazione. Di seguito è riportato il codice che applica gli stili dell'area visibile predefiniti in base alle specifiche:
scoped_refptr<ComputedStyle> StyleResolver::StyleForViewport() {
scoped_refptr<ComputedStyle> viewport_style =
InitialStyleForElement(GetDocument());
viewport_style->SetZIndex(0);
viewport_style->SetIsStackingContextWithoutContainment(true);
viewport_style->SetDisplay(EDisplay::kBlock);
viewport_style->SetPosition(EPosition::kAbsolute);
viewport_style->SetOverflowX(EOverflow::kAuto);
viewport_style->SetOverflowY(EOverflow::kAuto);
// …
return viewport_style;
}
Non è necessario conoscere C++ o le complessità del motore di stile di Blink per vedere che questo codice gestisce i valori z-index
, display
, position
e overflow
dell'area visibile (o, in modo più preciso, del blocco iniziale contenitore). Si tratta di concetti che potresti conoscere dal CSS. Esistono altri elementi magici relativi ai contesti di sovrapposizione, che non si traducono direttamente in una proprietà CSS, ma nel complesso potresti considerare questo oggetto viewport
come qualcosa che può essere definito con CSS da Blink, proprio come un elemento DOM, tranne che non fa parte del DOM.
Ci dà esattamente ciò che vogliamo. Possiamo applicare i nostri stili filter
all'oggetto viewport
, che influisce visivamente sul rendering, senza interferire in alcun modo con gli stili di pagina osservabili o con il DOM.
Conclusione
Per ricapitolare il nostro piccolo viaggio, abbiamo iniziato creando un prototipo utilizzando la tecnologia web anziché C++, quindi abbiamo iniziato a spostarne parti nel Renderer Blink.
- Innanzitutto, abbiamo reso il nostro prototipo più autonomo inserendo in linea gli URL dei dati.
- Abbiamo quindi reso questi URL di dati interni ottimizzati per i CSP, inserendo il loro caricamento in caratteri speciali.
- Abbiamo reso la nostra implementazione indipendente dal DOM e non osservabile tramite programmazione spostando gli stili in
viewport
interno a Blink.
La particolarità di questa implementazione è che il nostro prototipo HTML/CSS/SVG ha finito per influenzare il design tecnico finale. Abbiamo trovato un modo per utilizzare la piattaforma web, anche all'interno del Renderer Blink.
Per ulteriori informazioni, consulta la nostra proposta di design o il bug di monitoraggio di Chromium che fa riferimento a tutte le patch correlate.
Scaricare i canali in anteprima
Valuta la possibilità di utilizzare Chrome Canary, Dev o Beta come browser di sviluppo predefinito. Questi canali di anteprima ti consentono di accedere alle funzionalità più recenti di DevTools, di testare API di piattaforme web all'avanguardia e di trovare i problemi sul tuo sito prima che lo facciano gli utenti.
Contatta il team di Chrome DevTools
Utilizza le seguenti opzioni per discutere di nuove funzionalità, aggiornamenti o qualsiasi altro argomento relativo a DevTools.
- Inviaci feedback e richieste di funzionalità all'indirizzo crbug.com.
- Segnala un problema DevTools utilizzando Altre opzioni > Guida > Segnala un problema DevTools in DevTools.
- Invia un tweet all'account @ChromeDevTools.
- Lascia commenti sulle novità nei video di YouTube di DevTools o sui video di YouTube con i suggerimenti per gli strumenti per sviluppatori.