Page MenuHomePhabricator

Use CSS filters to change icon colors
Open, Needs TriagePublic

Description

CSS filters (not to be confused with svg filters) are quite widely supported now (exceptions: Edge 12, IE11 and Opera Mini)
https://caniuse.com/#search=css%20filter

Luckily feature queries are supported by Opera Mini and all browsers that support SVG filters (https://caniuse.com/#search=feature%20queries)

Thus we can provide icon variants for free with CSS filters using PNG (uri) fallbacks.

Currently 3 icon variants require 3 data uris which can be quite large.

I propose we go back to 1, and use css filters to color icons accordingly
https://codepen.io/anon/pen/VdNWGy

Update: CodePen with an OOUI derived, progressive, frameless button and a normal one with CSS filter and same icon:
https://codepen.io/Volker_E/pen/pxPRLM?editors=1100

Note: For this to work, an SVG must be added to the HTML either via JS or HTML which defines the available variant filters. The required matrixes for these filters can be derived from rgb values.

acceptance criteria

  • PNGs are generated for all icons
  • Move all SVG background image declarations into a feature query
  • Add support for a progressive variant.

Icons would need to be progressive by default (blue) to support conversion to all 4 variants.

Sample CSS output for 1 icon and 4 variants.

// progressive is default
.mw-ui-icon-minerva-clock:before {
   background-image: url(/w/load.php?modules=mobile.startup.images.variants&image=clock&format=rasterized&lang=en&skin=minerva&version=1s9zh6w);
}

.mw-ui-icon-minerva-clock-black:before {
        background-image: url(/w/load.php?modules=mobile.startup.images.variants&image=clock&variant=black&format=rasterized&lang=en&skin=minerva&version=1s9zh6w);
}
.mw-ui-icon-minerva-clock-gray:before {
        background-image: url(/w/load.php?modules=mobile.startup.images.variants&image=clock&variant=gray&format=rasterized&lang=en&skin=minerva&version=1s9zh6w);
}
.mw-ui-icon-minerva-clock-invert:before {
        background-image: url(/w/load.php?modules=mobile.startup.images.variants&image=clock&variant=invert&format=rasterized&lang=en&skin=minerva&version=1s9zh6w);
}


@supports ( filter: invert( 0 ) ) {
    .mw-ui-icon-minerva-clock-black:before,
    .mw-ui-icon-minerva-clock:before,
    .mw-ui-icon-minerva-clock-gray:before,
    .mw-ui-icon-minerva-clock-invert:before {
       // must be a progressive icon
        background-image: linear-gradient(transparent,transparent),url(data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%222…0zm-1.037 5v4.524l3.904 2.298.66-1.1-3.192-1.877V5H10.39z%22/%3E%3C/svg%3E);
    }
   .mw-ui-icon-minerva-clock:before {
    filter: brightness(0);
  }
   .mw-ui-icon-minerva-clock-gray:before {
    filter: contrast(0.05);
  }
   .mw-ui-icon-minerva-clock-invert:before {
    filter: invert(1);
  }
}

Developer notes

Next steps - we should setup a code pen for one icon and test in the problematic browsers.
A patch was written: https://gerrit.wikimedia.org/r/#/c/mediawiki/core/+/463380/ - for OOUI 50% of the CSS module was trimmed
but it wasn't clear what we should do for older browsers.

Event Timeline

Volker_E renamed this task from Use svg filters to change icon colors to Use SVG filters to change icon colors.Sep 11 2018, 3:49 PM

From today's front-end developer group meeting:

Given the extra SVG element with matrix filter in HTML might be a) overly complex and b) run into caching issues on Special:Pages as HTML and CSS run out of sync – which in turn could be addressed by outputting SVG element through JS, but that would add to a), I think relying on

  • a combination of CSS filters might be sufficient to start from a black icon
    • and provide all color hues and opacities needed for states as long as we don't add multi-color icons imagery.
    • It would also separate cleaner presentation from content reyling only on CSS
    • we could continue using our current setup with all the different images and progressively enhance by @support feature query setting filters and resetting background-images

We need to make sure though, that the

  • browsers don't load the icons reset in @support feature queries and also, that
  • the CSS filters are applied consistently across browsers.

I've seen a minor issue of different resulting hues in a test a while ago at https://gerrit.wikimedia.org/r/#/c/oojs/ui/+/324014/

Change 463373 had a related patch set uploaded (by Jdlrobson; owner: Jdlrobson):
[mediawiki/skins/MinervaNeue@master] Use default filters baked into core for last modified

https://gerrit.wikimedia.org/r/463373

This comment was removed by Jdlrobson.

Change 463380 had a related patch set uploaded (by Jdlrobson; owner: Jdlrobson):
[mediawiki/core@master] Allow icon variants without the additional image

https://gerrit.wikimedia.org/r/463380

Change 463381 had a related patch set uploaded (by Jdlrobson; owner: Jdlrobson):
[mediawiki/core@master] OOUI icon packs use CSS filters rather than image assets for inverted icons

https://gerrit.wikimedia.org/r/463381

I experimented with this and for little risk to old browsers, we can have gray, black and white icons using contrast and invert filters.

If we want progressive icons, right now I don't see any way to do that without adding a custom CSS filter (none of the predefined ones seem to be able to color icons that initially have no color).

Big wins to be had here in OOUI with just the inverted icon:

mw.inspect();
var ooIcons = Object.keys(mw.loader.moduleRegistry).filter((key) => key.indexOf( 'oojs-ui.styles.icons' ) > -1);
mw.loader.using(ooIcons).then( function () {
    console.log(ooIcons.map((name)=> mw.inspect.getModuleSize(name)).reduce((a,b) => a + b, 0 ))
} );

Before: 401, 164
After: 176, 040

@Jdlrobson Check you compare the transfer size of the request that included the module instead? Compression is a big deal, even more so in a batch with other stylesheets. Requesting them in isolation, or without compression, or from mw.inspect is not representative.

I intend to remove the raw sizes from mw.inspect in the next quarter when we work on T194684, in favour of Resource Timing information which exposes the transfer sizes instead. Apart from the aforementioned skews and misrepresentation, it also means it can correctly report 0 for cache hits from localStorage and/or browser cache.

Is there an easy way to generate the size? I guess I'd need to construct a RL url using Object.keys(mw.loader.moduleRegistry).filter((key) => key.indexOf( 'oojs-ui.styles.icons' ) > -1) ?

View or make a page that uses them in some representative way, then with "Disable cache" checked and local storage cleared once, reload the page and check the transfer size in DevTools / Network.

export.png (1×2 px, 176 KB)

(This is from prod, but the same applies locally.)

iew or make a page that uses them in some representative way

That's where i'm struggling. I can't find one that uses all of them :) I'll construct the URI...

iew or make a page that uses them in some representative way

That's where i'm struggling. I can't find one that uses all of them :)

Let's use realistic examples. If common usage only uses a few of them, our comparison should also use a few of them only. Otherwise we're optimising something that users wouldn't actually experience.

Separate from this, we can speculate how much bandwidth we're saving humanity overall. But that's a separate metric we'd measure and present in a different way.

Let's use realistic examples.

Sorry. I didn't mean to suggest this was not realistic. VisualEditor loads all of these icons. I just didn't have VisualEditor setup locally so I couldn't test.

I can see it uses all of these icons like so:

> ooModules = Object.keys(mw.loader.moduleRegistry).filter((key) => key.indexOf( 'oojs-ui.styles.icons' ) > -1);
> readyModules = ooModules.filter((key)=>mw.loader.getState(key) === 'ready')
> readyModules.length
> 0

Click edit.

> readyModules = ooModules.filter((key)=>mw.loader.getState(key) === 'ready').length
> readyModules.length
> 15

Before change: 26622
content-length: 21157

So 5k after gzipping

Another point in the replacement of invert icons is, that we would need to remove the SVG fallback for browsers which don't support filter immediately otherwise we have the PNG fallback reference, the embedded inverted icon inlined and the filter property in the CSS. Increased file size, not gain.

My last comment was about relying only on CSS filter not about the current task description of SVG filters.
And the problem of relying only on them would affect IE9-11 & Edge 12 and Opera Mini which sounds like a blocker to me – pushing at best 1x PNG fallbacks at it?

image.png (1×2 px, 204 KB)

Jdlrobson renamed this task from Use SVG filters to change icon colors to Use CSS filters to change icon colors.Oct 4 2018, 11:33 PM
Jdlrobson updated the task description. (Show Details)
Jdlrobson updated the task description. (Show Details)

From conversation with @Jdlrobson today. How I would see the best setup:

  1. Have PNGs (to decide if 1x or 2x or nx) as icons default per background-image
  2. Use SVG (only one color progressive for being able to hue-rotate) of each icon within @supports query and apply filters to corresponding variants

This way we make sure, that browsers without support fallback to PNGs and load only one image per icon variants classes.

Provided CodePen with an OOUI derived, progressive, frameless button icon+label output with CSS filter https://codepen.io/Volker_E/pen/pxPRLM?editors=1100

Benefits:

  • more shades easily possible
  • less data (to be evaluated) how much this gains us
  • nice transitions between action item states possible through filter, no need to inline other state icons

Cautious about:

  • Browser support of filters, QAing strongly advised.
    • Opera Mini and IE 11 would fallback to PNGs. IE 11 on HiDPI would probably see a minor regression, but due to the nature of IE 11's natural environment that might be a negligible number of folks.
    • Could it go wrong anywhere in filter supporting browsers due to browser quirks?! Resulting in showing progressive icons in non-actionable places. Possible intermediate way to deal with this would be to rely on Accent10 icon as starting point

Change 463380 abandoned by Jdlrobson:
Allow icon variants without the additional image

Reason:
Please continue conversing on ticket

https://gerrit.wikimedia.org/r/463380

Change 463373 abandoned by Jdlrobson:
Use default filters baked into core for last modified

Reason:
Please continue conversation on ticket.

https://gerrit.wikimedia.org/r/463373

Change 463381 abandoned by Jdlrobson:
OOUI icon packs use CSS filters rather than image assets for inverted icons

Reason:
please continue discussion on phab ticket

https://gerrit.wikimedia.org/r/463381

Will this task be superseded by T282625?

Potentially, but the mobile site currently doesn't Vue.js and is unable to at current time, so a shorter term solution as described above may be warranted.

Potentially, but the mobile site currently doesn't Vue.js and is unable to at current time, so a shorter term solution as described above may be warranted.

given that the Vue summit is coming up I'd love to understand what you mean by "unable to at current time"?

given that the Vue summit is coming up I'd love to understand what you mean by "unable to at current time"?

Right now we do not have server-side rendering capabilities (T272878) or a guarantee of being able to use it in this way, as a result we cannot use Vue.js for mobile or Vector for non-JavaScript users. As a result we use mw-ui-icon which does not have the capabilities that WVUI does. It follows that CSS filters may be needed as an interim step depending on the timeline of T272878.