Имитация недостатков цветового зрения в Blink Renderer

В этой статье описывается, почему и как мы реализовали симуляцию дефицита цветового зрения в DevTools и Blink Renderer.

Фон: плохой цветовой контраст

Низкоконтрастный текст — наиболее распространенная автоматически обнаруживаемая проблема доступности в Интернете.

Список распространенных проблем доступности в Интернете. Низкоконтрастный текст — самая распространенная проблема.

Согласно анализу доступности WebAIM 1 миллиона лучших веб-сайтов , более 86% домашних страниц имеют низкий контраст. В среднем на каждой домашней странице имеется 36 отдельных экземпляров текста с низким контрастом.

Использование DevTools для поиска, понимания и устранения проблем с контрастностью

Chrome DevTools может помочь разработчикам и дизайнерам улучшить контрастность и выбрать более доступные цветовые схемы для веб-приложений:

Недавно мы добавили в этот список новый инструмент, и он немного отличается от других. Вышеуказанные инструменты в основном фокусируются на отображении информации о коэффициенте контрастности и предоставлении вам вариантов ее исправления . Мы поняли, что DevTools все еще не хватает способа для разработчиков получить более глубокое понимание этого проблемного пространства. Чтобы решить эту проблему, мы реализовали имитацию дефицита зрения на вкладке DevTools Rendering.

В Puppeteer новый API page.emulateVisionDeficiency(type) позволяет программно включать эти симуляции.

Нарушения цветового зрения

Примерно 1 из 20 человек страдает от дефицита цветового зрения (также известного как менее точный термин «дальтонизм»). Такие нарушения затрудняют различение цветов, что может усугубить проблемы с контрастностью .

Красочная картинка из расплавленных мелков, без имитации нарушений цветового зрения.
Красочная картинка, нарисованная расплавленными мелками , без имитации нарушений цветового зрения.
ALT_TEXT_HERE
Влияние имитации ахроматопсии на красочное изображение расплавленных мелков.
Влияние имитации дейтеранопии на красочное изображение расплавленных мелков.
Влияние имитации дейтеранопии на красочное изображение расплавленных мелков.
Влияние имитации протанопии на красочное изображение расплавленных мелков.
Влияние имитации протанопии на красочное изображение расплавленных мелков.
Влияние имитации тританопии на красочное изображение расплавленных мелков.
Влияние имитации тританопии на красочное изображение расплавленных мелков.

Как разработчик с нормальным зрением, вы можете увидеть, что DevTools отображает плохой коэффициент контрастности для цветовых пар, которые визуально выглядят нормально для вас. Это происходит потому, что формулы коэффициента контрастности учитывают эти недостатки цветового зрения! Вы все равно можете читать текст с низкой контрастностью в некоторых случаях, но люди с нарушениями зрения не имеют такой привилегии.

Позволяя дизайнерам и разработчикам моделировать влияние этих недостатков зрения на их собственные веб-приложения, мы стремимся предоставить недостающее звено: DevTools не только поможет вам найти и исправить проблемы с контрастностью, но теперь вы также сможете их понять !

Имитация нарушений цветового зрения с помощью HTML, CSS, SVG и C++

Прежде чем мы углубимся в реализацию нашей функции с помощью Blink Renderer, полезно понять, как можно реализовать эквивалентную функциональность с использованием веб-технологий.

Вы можете думать о каждой из этих симуляций дефицита цветового зрения как о наложении, покрывающем всю страницу. Веб-платформа имеет способ сделать это: CSS-фильтры! С помощью свойства CSS- filter вы можете использовать некоторые предопределенные функции фильтра, такие как blur , contrast , grayscale , hue-rotate и многие другие. Для еще большего контроля свойство filter также принимает URL-адрес, который может указывать на пользовательское определение фильтра SVG:

<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>

В приведенном выше примере используется пользовательское определение фильтра на основе цветовой матрицы. Концептуально, значение цвета каждого пикселя [Red, Green, Blue, Alpha] умножается на матрицу для создания нового цвета [R′, G′, B′, A′] .

Каждая строка в матрице содержит 5 значений: множитель для (слева направо) R, G, B и A, а также пятое значение для постоянного значения сдвига. Имеется 4 строки: первая строка матрицы используется для вычисления нового значения Red, вторая строка Green, третья строка Blue и последняя строка Alpha.

Вам может быть интересно, откуда взялись точные числа в нашем примере. Что делает эту цветовую матрицу хорошим приближением дейтеранопии? Ответ: наука! Значения основаны на физиологически точной модели имитации дефицита цветового зрения Мачадо, Оливейры и Фернандеса .

В любом случае, у нас есть этот фильтр SVG, и теперь мы можем применить его к произвольным элементам на странице с помощью CSS. Мы можем повторить тот же шаблон для других нарушений зрения. Вот демо того, как это выглядит:

Если бы мы захотели, мы могли бы построить нашу функцию DevTools следующим образом: когда пользователь эмулирует недостаток зрения в пользовательском интерфейсе DevTools, мы внедряем фильтр SVG в проверяемый документ, а затем применяем стиль фильтра к корневому элементу. Однако с этим подходом связано несколько проблем:

  • На странице уже может быть установлен фильтр в корневом элементе, который наш код может переопределить.
  • На странице уже может быть элемент с id="deuteranopia" , конфликтующий с нашим определением фильтра.
  • Страница может полагаться на определенную структуру DOM, и, вставив <svg> в DOM, мы можем нарушить эти предположения.

Оставив в стороне крайние случаи, главная проблема этого подхода заключается в том, что мы будем вносить программно наблюдаемые изменения на страницу . Если пользователь DevTools проверит DOM, он может внезапно увидеть элемент <svg> , который он никогда не добавлял, или filter CSS, который он никогда не писал. Это было бы запутанно! Чтобы реализовать эту функциональность в DevTools, нам нужно решение, которое не имеет этих недостатков.

Давайте посмотрим, как сделать это менее навязчивым. В этом решении есть две части, которые нам нужно скрыть: 1) стиль CSS со свойством filter и 2) определение фильтра SVG, которое в настоящее время является частью 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>

Избегание зависимости SVG в документе

Давайте начнем с части 2: как избежать добавления SVG в DOM? Одна из идей — переместить его в отдельный файл SVG. Мы можем скопировать <svg>…</svg> из HTML выше и сохранить его как filter.svg — но сначала нам нужно внести некоторые изменения! Встроенный SVG в HTML следует правилам анализа HTML. Это означает, что в некоторых случаях вы можете обойтись без таких вещей, как пропуск кавычек вокруг значений атрибутов . Однако SVG в отдельных файлах должен быть допустимым XML — а анализ XML гораздо более строг, чем HTML. Вот наш фрагмент 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>

Чтобы сделать этот валидный автономный 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>

Первое изменение — это объявление пространства имен XML вверху. Второе дополнение — так называемый «солидус» — косая черта, которая указывает, что тег <feColorMatrix> как открывает, так и закрывает элемент. Последнее изменение на самом деле не является необходимым (мы могли бы просто придерживаться явного закрывающего тега </feColorMatrix> вместо этого), но поскольку и XML, и SVG-in-HTML поддерживают это сокращение /> , мы могли бы его использовать.

В любом случае, с этими изменениями мы наконец можем сохранить это как допустимый файл SVG и указать на него из значения свойства filter CSS в нашем HTML-документе:

<style>
  :root {
    filter: url(filters.svg#deuteranopia);
  }
</style>

Ура, нам больше не нужно вставлять SVG в документ! Это уже намного лучше. Но... теперь мы зависим от отдельного файла. Это все еще зависимость. Можно ли как-то от нее избавиться?

Как оказалось, нам на самом деле не нужен файл. Мы можем закодировать весь файл в URL, используя URL-адрес данных. Чтобы это произошло, мы буквально берем содержимое файла SVG, который у нас был раньше, добавляем префикс data: настраиваем правильный тип MIME, и получаем действительный URL-адрес данных, который представляет тот же самый файл 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>

Преимущество в том, что теперь нам больше не нужно хранить файл где-либо или загружать его с диска или по сети, чтобы использовать его в нашем HTML-документе. Поэтому вместо того, чтобы ссылаться на имя файла, как мы делали раньше, теперь мы можем указать на URL-адрес данных:

<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>

В конце URL мы по-прежнему указываем идентификатор фильтра, который хотим использовать, как и раньше. Обратите внимание, что нет необходимости кодировать документ SVG в Base64 в URL — это только ухудшит читаемость и увеличит размер файла. Мы добавили обратные косые черты в конце каждой строки, чтобы гарантировать, что символы новой строки в URL-адресе данных не завершат строковый литерал CSS.

До сих пор мы говорили только о том, как имитировать дефекты зрения с помощью веб-технологий. Интересно, что наша окончательная реализация в Blink Renderer на самом деле довольно похожа. Вот вспомогательная утилита C++, которую мы добавили для создания URL-адреса данных с заданным определением фильтра, основанная на той же технике:

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;
}

И вот как мы используем его для создания всех необходимых нам фильтров :

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 "";
  }
}

Обратите внимание, что эта техника дает нам доступ ко всей мощи фильтров SVG без необходимости что-либо заново реализовывать или изобретать велосипеды. Мы реализуем функцию Blink Renderer, но делаем это, используя веб-платформу.

Итак, мы выяснили, как создавать фильтры SVG и превращать их в URL-адреса данных, которые мы можем использовать в нашем значении свойства filter CSS. Можете ли вы придумать проблему с этой техникой? Оказывается, мы не можем полагаться на загрузку URL-адреса данных во всех случаях, поскольку целевая страница может иметь Content-Security-Policy , которая блокирует URL-адреса данных. Наша окончательная реализация уровня Blink уделяет особое внимание обходу CSP для этих «внутренних» URL-адресов данных во время загрузки.

Оставив в стороне пограничные случаи, мы добились хорошего прогресса. Поскольку мы больше не зависим от наличия встроенного <svg> в том же документе, мы фактически сократили наше решение до одного-единственного определения свойства filter CSS. Отлично! Теперь давайте избавимся и от этого.

Избегание зависимости CSS в документе

Подведем итоги: вот где мы находимся на данный момент:

<style>
  :root {
    filter: url('data:…');
  }
</style>

Мы все еще зависим от этого свойства filter CSS, которое может переопределить filter в реальном документе и сломать все. Это также будет отображаться при проверке вычисленных стилей в DevTools, что может сбить с толку. Как нам избежать этих проблем? Нам нужно найти способ добавить фильтр в документ, не делая его программно наблюдаемым для разработчиков.

Одна из идей, которая пришла в голову, заключалась в создании нового внутреннего свойства CSS Chrome, которое ведет себя как filter , но имеет другое имя, например --internal-devtools-filter . Затем мы могли бы добавить специальную логику, чтобы гарантировать, что это свойство никогда не будет отображаться в DevTools или в вычисляемых стилях в DOM. Мы могли бы даже убедиться, что оно работает только с одним элементом, для которого оно нам нужно: корневым элементом. Однако это решение не было бы идеальным: мы бы дублировали функциональность, которая уже существует с filter , и даже если мы постараемся скрыть это нестандартное свойство, веб-разработчики все равно могут узнать о нем и начать его использовать, что было бы плохо для веб-платформы. Нам нужен какой-то другой способ применения стиля CSS без того, чтобы он был виден в DOM. Есть идеи?

В спецификации CSS есть раздел, в котором представлена ​​модель визуального форматирования, которую она использует, и одним из ключевых понятий там является viewport . Это визуальное представление, через которое пользователи просматривают веб-страницу. Тесно связанное понятие — начальный содержащий блок , который является своего рода стилем viewport <div> , существующим только на уровне спецификации. Спецификация ссылается на эту концепцию «viewport» повсюду. Например, вы знаете, как браузер показывает полосы прокрутки, когда содержимое не помещается? Все это определено в спецификации CSS на основе этого «viewport».

Этот viewport также существует в Blink Renderer, как деталь реализации. Вот код , который применяет стили viewport по умолчанию в соответствии со спецификацией:

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;
}

Вам не нужно понимать C++ или тонкости движка стилей Blink, чтобы увидеть, что этот код обрабатывает z-index , display , position и overflow viewport (или, точнее, начального содержащего блока). Все эти концепции вам, возможно, знакомы из CSS! Есть еще одна магия, связанная с контекстами стекирования, которая напрямую не транслируется в свойство CSS, но в целом вы можете думать об этом объекте viewport как о чем-то, что можно стилизовать с помощью CSS из Blink, как элемент DOM — за исключением того, что он не является частью DOM.

Это дает нам именно то, что мы хотим! Мы можем применить наши стили filter к объекту viewport , что визуально влияет на рендеринг, не вмешиваясь никоим образом в наблюдаемые стили страницы или DOM.

Заключение

Подводя итоги нашего небольшого путешествия, мы начали с создания прототипа с использованием веб-технологий вместо C++, а затем начали работать над переносом его частей в Blink Renderer.

  • Сначала мы сделали наш прототип более автономным, встроив в него URL-адреса данных.
  • Затем мы сделали эти внутренние URL-адреса данных дружественными к CSP, выделив их загрузку особым образом.
  • Мы сделали нашу реализацию независимой от DOM и программно незаметной, переместив стили во внутреннюю viewport Blink.

Уникальность этой реализации в том, что наш прототип HTML/CSS/SVG в конечном итоге повлиял на окончательный технический дизайн. Мы нашли способ использовать веб-платформу, даже в Blink Renderer!

Для получения более подробной информации ознакомьтесь с нашим предложением по дизайну или с отслеживанием ошибок Chromium, в котором указаны все соответствующие исправления.

Загрузите каналы предварительного просмотра

Рассмотрите возможность использования Chrome Canary , Dev или Beta в качестве браузера для разработки по умолчанию. Эти каналы предварительного просмотра предоставляют вам доступ к новейшим функциям DevTools, позволяют тестировать передовые API веб-платформ и помогают находить проблемы на вашем сайте до того, как это сделают ваши пользователи!

Свяжитесь с командой Chrome DevTools

Используйте следующие варианты для обсуждения новых функций, обновлений или чего-либо еще, связанного с DevTools.

,

В этой статье описывается, почему и как мы реализовали симуляцию дефицита цветового зрения в DevTools и Blink Renderer.

Фон: плохой цветовой контраст

Низкоконтрастный текст — наиболее распространенная автоматически обнаруживаемая проблема доступности в Интернете.

Список распространенных проблем доступности в Интернете. Низкоконтрастный текст — самая распространенная проблема.

Согласно анализу доступности WebAIM 1 миллиона лучших веб-сайтов , более 86% домашних страниц имеют низкий контраст. В среднем на каждой домашней странице имеется 36 отдельных экземпляров текста с низким контрастом.

Использование DevTools для поиска, понимания и устранения проблем с контрастностью

Chrome DevTools может помочь разработчикам и дизайнерам улучшить контрастность и выбрать более доступные цветовые схемы для веб-приложений:

Недавно мы добавили в этот список новый инструмент, и он немного отличается от других. Вышеуказанные инструменты в основном фокусируются на отображении информации о коэффициенте контрастности и предоставлении вам вариантов ее исправления . Мы поняли, что DevTools все еще не хватает способа для разработчиков получить более глубокое понимание этого проблемного пространства. Чтобы решить эту проблему, мы реализовали имитацию дефицита зрения на вкладке DevTools Rendering.

В Puppeteer новый API page.emulateVisionDeficiency(type) позволяет программно включать эти симуляции.

Нарушения цветового зрения

Примерно 1 из 20 человек страдает от дефицита цветового зрения (также известного как менее точный термин «дальтонизм»). Такие нарушения затрудняют различение цветов, что может усугубить проблемы с контрастностью .

Красочная картинка из расплавленных мелков, без имитации нарушений цветового зрения.
Красочная картинка, нарисованная расплавленными мелками , без имитации нарушений цветового зрения.
ALT_TEXT_HERE
Влияние имитации ахроматопсии на красочное изображение расплавленных мелков.
Влияние имитации дейтеранопии на красочное изображение расплавленных мелков.
Влияние имитации дейтеранопии на красочное изображение расплавленных мелков.
Влияние имитации протанопии на красочное изображение расплавленных мелков.
Влияние имитации протанопии на красочное изображение расплавленных мелков.
Влияние имитации тританопии на красочное изображение расплавленных мелков.
Влияние имитации тританопии на красочное изображение расплавленных мелков.

Как разработчик с нормальным зрением, вы можете увидеть, что DevTools отображает плохой коэффициент контрастности для цветовых пар, которые визуально выглядят нормально для вас. Это происходит потому, что формулы коэффициента контрастности учитывают эти недостатки цветового зрения! Вы все равно можете читать текст с низкой контрастностью в некоторых случаях, но люди с нарушениями зрения не имеют такой привилегии.

Позволяя дизайнерам и разработчикам моделировать влияние этих недостатков зрения на их собственные веб-приложения, мы стремимся предоставить недостающее звено: DevTools не только поможет вам найти и исправить проблемы с контрастностью, но теперь вы также сможете их понять !

Имитация нарушений цветового зрения с помощью HTML, CSS, SVG и C++

Прежде чем мы углубимся в реализацию нашей функции с помощью Blink Renderer, полезно понять, как можно реализовать эквивалентную функциональность с использованием веб-технологий.

Вы можете думать о каждой из этих симуляций дефицита цветового зрения как о наложении, покрывающем всю страницу. Веб-платформа имеет способ сделать это: CSS-фильтры! С помощью свойства CSS- filter вы можете использовать некоторые предопределенные функции фильтра, такие как blur , contrast , grayscale , hue-rotate и многие другие. Для еще большего контроля свойство filter также принимает URL-адрес, который может указывать на пользовательское определение фильтра SVG:

<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>

В приведенном выше примере используется пользовательское определение фильтра на основе цветовой матрицы. Концептуально, значение цвета каждого пикселя [Red, Green, Blue, Alpha] умножается на матрицу для создания нового цвета [R′, G′, B′, A′] .

Каждая строка в матрице содержит 5 значений: множитель для (слева направо) R, G, B и A, а также пятое значение для постоянного значения сдвига. Имеется 4 строки: первая строка матрицы используется для вычисления нового значения Red, вторая строка Green, третья строка Blue и последняя строка Alpha.

Вам может быть интересно, откуда взялись точные числа в нашем примере. Что делает эту цветовую матрицу хорошим приближением дейтеранопии? Ответ: наука! Значения основаны на физиологически точной модели имитации дефицита цветового зрения Мачадо, Оливейры и Фернандеса .

В любом случае, у нас есть этот фильтр SVG, и теперь мы можем применить его к произвольным элементам на странице с помощью CSS. Мы можем повторить тот же шаблон для других нарушений зрения. Вот демо того, как это выглядит:

Если бы мы захотели, мы могли бы построить нашу функцию DevTools следующим образом: когда пользователь эмулирует недостаток зрения в пользовательском интерфейсе DevTools, мы внедряем фильтр SVG в проверяемый документ, а затем применяем стиль фильтра к корневому элементу. Однако с этим подходом связано несколько проблем:

  • На странице уже может быть установлен фильтр в корневом элементе, который наш код может переопределить.
  • На странице уже может быть элемент с id="deuteranopia" , конфликтующий с нашим определением фильтра.
  • Страница может полагаться на определенную структуру DOM, и, вставив <svg> в DOM, мы можем нарушить эти предположения.

Оставив в стороне крайние случаи, главная проблема этого подхода заключается в том, что мы будем вносить программно наблюдаемые изменения на страницу . Если пользователь DevTools проверит DOM, он может внезапно увидеть элемент <svg> , который он никогда не добавлял, или filter CSS, который он никогда не писал. Это было бы запутанно! Чтобы реализовать эту функциональность в DevTools, нам нужно решение, которое не имеет этих недостатков.

Давайте посмотрим, как сделать это менее навязчивым. В этом решении есть две части, которые нам нужно скрыть: 1) стиль CSS со свойством filter и 2) определение фильтра SVG, которое в настоящее время является частью 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>

Избегание зависимости SVG в документе

Давайте начнем с части 2: как избежать добавления SVG в DOM? Одна из идей — переместить его в отдельный файл SVG. Мы можем скопировать <svg>…</svg> из HTML выше и сохранить его как filter.svg — но сначала нам нужно внести некоторые изменения! Встроенный SVG в HTML следует правилам анализа HTML. Это означает, что в некоторых случаях вы можете обойтись без таких вещей, как пропуск кавычек вокруг значений атрибутов . Однако SVG в отдельных файлах должен быть допустимым XML — а анализ XML гораздо более строг, чем HTML. Вот наш фрагмент 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>

Чтобы сделать этот валидный автономный 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>

Первое изменение — это объявление пространства имен XML вверху. Второе дополнение — так называемый «солидус» — косая черта, которая указывает, что тег <feColorMatrix> как открывает, так и закрывает элемент. Последнее изменение на самом деле не является необходимым (мы могли бы просто придерживаться явного закрывающего тега </feColorMatrix> вместо этого), но поскольку и XML, и SVG-in-HTML поддерживают это сокращение /> , мы могли бы его использовать.

В любом случае, с этими изменениями мы наконец можем сохранить это как допустимый файл SVG и указать на него из значения свойства filter CSS в нашем HTML-документе:

<style>
  :root {
    filter: url(filters.svg#deuteranopia);
  }
</style>

Ура, нам больше не нужно вставлять SVG в документ! Это уже намного лучше. Но... теперь мы зависим от отдельного файла. Это все еще зависимость. Можно ли как-то от нее избавиться?

Как оказалось, нам на самом деле не нужен файл. Мы можем закодировать весь файл в URL, используя URL-адрес данных. Чтобы это произошло, мы буквально берем содержимое файла SVG, который у нас был раньше, добавляем префикс data: настраиваем правильный тип MIME, и получаем действительный URL-адрес данных, который представляет тот же самый файл 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>

Преимущество в том, что теперь нам больше не нужно хранить файл где-либо или загружать его с диска или по сети, чтобы использовать его в нашем HTML-документе. Поэтому вместо того, чтобы ссылаться на имя файла, как мы делали раньше, теперь мы можем указать на URL-адрес данных:

<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>

В конце URL мы по-прежнему указываем идентификатор фильтра, который хотим использовать, как и раньше. Обратите внимание, что нет необходимости кодировать документ SVG в Base64 в URL — это только ухудшит читаемость и увеличит размер файла. Мы добавили обратные косые черты в конце каждой строки, чтобы гарантировать, что символы новой строки в URL-адресе данных не завершат строковый литерал CSS.

До сих пор мы говорили только о том, как имитировать дефекты зрения с помощью веб-технологий. Интересно, что наша окончательная реализация в Blink Renderer на самом деле довольно похожа. Вот вспомогательная утилита C++, которую мы добавили для создания URL-адреса данных с заданным определением фильтра, основанная на той же технике:

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;
}

И вот как мы используем его для создания всех необходимых нам фильтров :

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 "";
  }
}

Обратите внимание, что эта техника дает нам доступ ко всей мощи фильтров SVG без необходимости что-либо заново реализовывать или изобретать велосипеды. Мы реализуем функцию Blink Renderer, но делаем это, используя веб-платформу.

Итак, мы выяснили, как создавать фильтры SVG и превращать их в URL-адреса данных, которые мы можем использовать в нашем значении свойства filter CSS. Можете ли вы придумать проблему с этой техникой? Оказывается, мы не можем полагаться на загрузку URL-адреса данных во всех случаях, поскольку целевая страница может иметь Content-Security-Policy , которая блокирует URL-адреса данных. Наша окончательная реализация уровня Blink уделяет особое внимание обходу CSP для этих «внутренних» URL-адресов данных во время загрузки.

Оставив в стороне пограничные случаи, мы добились хорошего прогресса. Поскольку мы больше не зависим от наличия встроенного <svg> в том же документе, мы фактически сократили наше решение до одного-единственного определения свойства filter CSS. Отлично! Теперь давайте избавимся и от этого.

Избегание зависимости CSS в документе

Подведем итоги: вот где мы находимся на данный момент:

<style>
  :root {
    filter: url('data:…');
  }
</style>

Мы все еще зависим от этого свойства filter CSS, которое может переопределить filter в реальном документе и сломать все. Это также будет отображаться при проверке вычисленных стилей в DevTools, что может сбить с толку. Как нам избежать этих проблем? Нам нужно найти способ добавить фильтр в документ, не делая его программно наблюдаемым для разработчиков.

Одна из идей, которая пришла в голову, заключалась в создании нового внутреннего свойства CSS Chrome, которое ведет себя как filter , но имеет другое имя, например --internal-devtools-filter . Затем мы могли бы добавить специальную логику, чтобы гарантировать, что это свойство никогда не будет отображаться в DevTools или в вычисляемых стилях в DOM. Мы могли бы даже убедиться, что оно работает только с одним элементом, для которого оно нам нужно: корневым элементом. Однако это решение не было бы идеальным: мы бы дублировали функциональность, которая уже существует с filter , и даже если мы постараемся скрыть это нестандартное свойство, веб-разработчики все равно могут узнать о нем и начать его использовать, что было бы плохо для веб-платформы. Нам нужен какой-то другой способ применения стиля CSS без того, чтобы он был виден в DOM. Есть идеи?

В спецификации CSS есть раздел, в котором представлена ​​модель визуального форматирования, которую она использует, и одним из ключевых понятий там является viewport . Это визуальное представление, через которое пользователи просматривают веб-страницу. Тесно связанное понятие — начальный содержащий блок , который является своего рода стилем viewport <div> , существующим только на уровне спецификации. Спецификация ссылается на эту концепцию «viewport» повсюду. Например, вы знаете, как браузер показывает полосы прокрутки, когда содержимое не помещается? Все это определено в спецификации CSS на основе этого «viewport».

Этот viewport также существует в Blink Renderer, как деталь реализации. Вот код , который применяет стили viewport по умолчанию в соответствии со спецификацией:

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;
}

Вам не нужно понимать C++ или тонкости движка стилей Blink, чтобы увидеть, что этот код обрабатывает z-index , display , position и overflow viewport (или, точнее, начального содержащего блока). Все эти концепции вам, возможно, знакомы из CSS! Есть еще одна магия, связанная с контекстами стекирования, которая напрямую не транслируется в свойство CSS, но в целом вы можете думать об этом объекте viewport как о чем-то, что можно стилизовать с помощью CSS из Blink, как элемент DOM — за исключением того, что он не является частью DOM.

Это дает нам именно то, что мы хотим! Мы можем применить наши стили filter к объекту viewport , что визуально влияет на рендеринг, не вмешиваясь никоим образом в наблюдаемые стили страницы или DOM.

Заключение

Подводя итоги нашего небольшого путешествия, мы начали с создания прототипа с использованием веб-технологий вместо C++, а затем начали работать над переносом его частей в Blink Renderer.

  • Сначала мы сделали наш прототип более автономным, встроив в него URL-адреса данных.
  • Затем мы сделали эти внутренние URL-адреса данных дружественными к CSP, выделив их загрузку особым образом.
  • Мы сделали нашу реализацию независимой от DOM и программно незаметной, переместив стили во внутреннюю viewport Blink.

Уникальность этой реализации в том, что наш прототип HTML/CSS/SVG в конечном итоге повлиял на окончательный технический дизайн. Мы нашли способ использовать веб-платформу, даже в Blink Renderer!

Для получения более подробной информации ознакомьтесь с нашим предложением по дизайну или с отслеживанием ошибок Chromium, в котором указаны все соответствующие исправления.

Загрузите каналы предварительного просмотра

Рассмотрите возможность использования Chrome Canary , Dev или Beta в качестве браузера для разработки по умолчанию. Эти каналы предварительного просмотра предоставляют вам доступ к новейшим функциям DevTools, позволяют тестировать передовые API веб-платформ и помогают находить проблемы на вашем сайте до того, как это сделают ваши пользователи!

Свяжитесь с командой Chrome DevTools

Используйте следующие варианты для обсуждения новых функций, обновлений или чего-либо еще, связанного с DevTools.