46

How do you prevent material icon text from showing up when Google's JS fails to convert them to icons?

Icons are defined in markup as such:

<span class="material-icons">icon_name</span>

Example: https://archive.fo/CKqKG/scr.png (see the top row of buttons).

Material Icons Documentation: https://material.io/icons/

This is also an issue in Google search where Google will actually read and save the div's text instead of ignoring it.

Example: https://i.stack.imgur.com/dKH1e.png

I understand that one solution is to simply switch to .PNGs (supplied by Google). I'd like to do whatever results in less (network) load on the user's system.

Thanks!

David
  • 554
  • 1
  • 5
  • 9

15 Answers15

17

you can use font-display: block;, just add this CSS to your HTML head:

<style>
   @font-face {
      font-family: 'Material Icons';
      font-display: block;
    }
</style>

for more information font-display

Fareed Alnamrouti
  • 30,771
  • 4
  • 85
  • 76
  • 3
    This will only work if you specify the src of the font in @font-face, and not use `@import` or a `link` tag in your head. Unfortunately, this is not (yet) possible with Google Fonts: https://css-tricks.com/dont-just-copy-the-font-face-out-of-google-fonts-urls/ – Laurens Mar 19 '19 at 20:56
  • 2
    Note: font-display: block will display an invisible font-face, which means that while the font is loading, an invisible text would be displayed which would have the same length as the fallback text. This ligature text is usually wider than the actual font, so you would need to set some styles so that even though it is invisible due to font-display:block, the width is still less so there is no flicker. – gaurav5430 Jun 29 '19 at 10:24
  • 3
    Old question, but new-to-me issue. You can now specify the font-display property with Google fonts, so https://fonts.googleapis.com/icon?family=Material+Icons&display=block works. – sheng Apr 20 '20 at 01:27
  • 5
    I tried your solution and it's working, but not when network speed is too slow. To be more clear, it will show block (actually nothing) instead of texts, for about two seconds, so it's fine when I try `fast 3G` mode and it will not show texts to the user, but for `slow 3G` mode, the problem is still there, just with about two seconds delay! Is there any way to increase this delay?! – Hamidreza Jul 01 '20 at 12:00
  • @Hamidreza I can +1 this it is still a major issue. – MrProgrammer Apr 21 '23 at 04:38
13

I've been struggling with a similar situation: my problem was not that the icons never loaded, just that they could take a while to load on slower connections and until they loaded ugly, unformatted text like sentiment_very_satisfied would be shown on the page (often many times larger than the surrounding text as well making it very obvious).

The other solutions here didn't work for me (including font-display:block which I thought might be promising), so I came up with my own using CSS and jQuery. I'm sure you could easily adapt it to use vanilla JS.

CSS:

.material-icons{
    opacity:0;
}

jQuery:

$(window).load(function() {
    $('.material-icons').css('opacity','1');
});

The trick here is that, unlike the more commonly used $(document).ready() listener, $(window).load() waits for all elements of a page to be downloaded before being triggered. In this case, that means it won't change the opacity of the icons until the icon font has been downloaded.

The downside is that the icons won't show up until everything on the page has been downloaded, but that was a trade-off I was willing to make to avoid having huge spans of text visible on my page before the icon font loaded.

(I also added a transition to the CSS .material-icons{transition:opacity 0.5s;} so they showed up nice and smooth.)

todd
  • 355
  • 3
  • 11
  • 3
    I just checked and `sentiment_very_satisfied` is a real icon: https://material.io/resources/icons/?icon=sentiment_very_satisfied&style=baseline. Yeah, that's what I want flashing my users. – Dem Pilafian Sep 29 '20 at 05:32
  • 1
    This, unfortunately, does not work in react. The querySelectorAll result is always empty in index.html of my project. Where do I put this code in the react project? – MrProgrammer Apr 21 '23 at 04:59
6

If you are using Typekit's webfont loader, you can apply conditional classes to hide the icons while the web font is loading or if it failed to load, e.g.:

.wf-loading, .wf-materialicons-n4-inactive {
  .material-icons {
    display: none;
  }
}

You can of course apply other styling techniques according to your preferences for best results, e.g. font-size: 0;, it will depend on your site and use case.

To load the material icons with the webfont loader, use configuration like so:

window.WebFontConfig = {
  google: {
    families: [
      'Material Icons',
    ],
  },
};
Adam Reis
  • 4,165
  • 1
  • 44
  • 35
  • can you kindly specify why the material icon fails to display and why it displays as text? and what could be the workaround? – Ramesh Pareek Feb 07 '18 at 12:49
  • 1
    There can be many reasons for material icons to fail to display, for example internet connection failure or otherwise a failed request to fetch the font files. In that case, if you use material icons with ligatures, when your code is something like `face`, the word "face" will be visible as plain text on your page. The work around in my answer prevents this text from showing up. – Adam Reis Feb 07 '18 at 21:50
6

In your "index.html", you should have the link to Google Material Icons. In this link, you have to include: "&display=block" at the end. I show you how it's the result:

  <link
  rel="stylesheet"
  href="https://fonts.googleapis.com/icon? 
  family=Material+Icons&display=block"
  crossorigin
  />

If you go now to the link, you will see that the document includes: "font-display: block;" (you have changed the "display").

And that's all. The name of the icon will not see more and the icon will charge correctly!

3

I am facing this same issue. I believe, though, that using a pseudo selector like i.material-icons:before can help. See this for more info.

---- EDIT : Working Example

i.material-icons:before{display:none;}

EMX
  • 6,066
  • 1
  • 25
  • 32
Ali
  • 72
  • 5
3

The right solution to this will be to add a max width of the same font-size and set overflow to hidden.

.material-icons {
    max-width: 16px;
    overflow: hidden;
}
3

I used to have the same problem (TOC), until I discovered that IcoMoon helps you create a css file with a class name identifier for each material icon you choose and also creates you the icon as a symbol SVG.

Let's see this with an example.

Google Fonts way (FOUC)

<span class="material-icons">
search
</span>

This code displays the search word while the icon font loads. However, with some good use of caching, preconnection, preload, and the display block, hopefully no one will pay attention to it.


IcoMoon way (Using Fonts)

<span class="icon-search"></span>

Here you can notice that there is no FOUC possibility because there is no default text to display.

.icon-search:before {
  content: "\e900";
}

With this extra class it will reserve an empty space until the source is loaded.


IcoMoon way (Using SVG symbol)

<symbol id="icon-search" viewBox="0 0 24 24">
<path d="M9.516 14.016q1.875 0 3.188-1.313t1.313-3.188-1.313-3.188-3.188-1.313-3.188 1.313-1.313 3.188 1.313 3.188 3.188 1.313zM15.516 14.016l4.969 4.969-1.5 1.5-4.969-4.969v-0.797l-0.281-0.281q-1.781 1.547-4.219 1.547-2.719 0-4.617-1.875t-1.898-4.594 1.898-4.617 4.617-1.898 4.594 1.898 1.875 4.617q0 0.984-0.469 2.227t-1.078 1.992l0.281 0.281h0.797z"></path>
</symbol>

<svg class="icon icon-search"><use xlink:href="#icon-search"></use></svg>

With this technique there is no need to wait for the fonts to load. BUT, if you have a large set of icons, the HTML size will be larger and maybe less maintainable.


Finally, if you're worried about the load time of your projects, you'll be glad to know that with this tool (IcoMoon) you only load the icons you use (OMG, this looks like an infomercial ). Anyway, here are the steps to follow:

  1. Go to https://icomoon.io/app/#/select/library and select Material icons with the + Add button.
  2. Now select those icons you are going to use.
  3. Finally, select at the bottom the option Generate Font or Generate SVG.

You will have a zip file with css and fonts you can include in your project or the SVG paths to include in your HTML.

Some extra references to read:

Pizaranha
  • 151
  • 7
  • 1
    Thanks for sharing this. Very handy to build a new font out of Material Icons (save a lot of 160kB in my case). A pity you can't change the weight and grade with Icomoon. – Kasper Kamperman Jun 22 '22 at 09:31
  • I am glad of hear that. What you mean with weight and grade? Maybe I could help. – Pizaranha Jul 27 '22 at 16:23
  • 1
    Weight and grade are new features in the icon font, to change stroke weight and emphasises on the outlines. See the parameters on the right: https://fonts.google.com/icons – Kasper Kamperman Aug 01 '22 at 08:17
2

In case if your are using angularjs, fix might be

<i class="material-icons-outlined" ng-bind-html=" 'icon_text' "></i>

or if you are using jquery, then

HTML

<i material-icons="icon_text" class="material-icons"></i>

jQuery.onLoad

$('[material-icons]').each(function(){
  var icon_text = $(this).attr('material-icons');
  $(this).html(icon_text)
});

Icon appears after the page has loaded.

2

instead of using material icons full text you can use their corresponding hex codepoints. of course this is not hiding but if the font is not loaded it just shows the unknown char symbol.

example:

<i class="material-icons">&#xE87D;</i>

you can find the list of codepoints at:

https://github.com/google/material-design-icons/issues/813#issuecomment-401601344

or

https://raw.githubusercontent.com/flutter/flutter/master/packages/flutter/lib/src/material/icons.dart

Hani
  • 121
  • 1
  • 5
1

Not a fix but on browsers that support preconnect you can try and load the fonts as soon as possible. Should help reduce the amount of time between text and icon being shown on slow connections.

<link rel="preconnect" href="//fonts.googleapis.com">
<link rel="preconnect" href="//fonts.gstatic.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
Neil
  • 8,925
  • 10
  • 44
  • 49
  • One of the very few things you could actually do. Surely not a fix, but it's still something. Unfortunately the issue is that the request returns stylesheet, that is next adding a request for the font itself. Which is most of the times at the end of the queue. Still something though. – Andrey Popov May 17 '21 at 11:25
1

I found that it helps if you insert - &display=swap at the end of the href in the link tag like so:

<link
    href="https://fonts.googleapis.com/icon?family=Material+Icons&display=swap"
    rel="stylesheet"
  >

here is a link for reference

Neveh
  • 323
  • 3
  • 7
  • 5
    "The easiest way to avoid showing invisible text while custom fonts load is to temporarily show a system font (...) swap tells the browser that text using the font should be displayed immediately using a system font. Once the custom font is ready, it replaces the system font." That is **exactly** the opposite of what the question is about. – Andrey Popov May 17 '21 at 11:23
1

Here is how I managed to minimize this issue. But note that it only works for Chrome and Edge, otherwise it will fallback to the usual behaviour. By using document.fonts.onloadingdone I check if the font I want is loaded. If it is loaded I create a class which will turn the opacity of the class 'material-icons' to 1 which was initially set as 0 in the css.

On a css file I write this rule (which will get overridden by the class which will be defined in the script eventually)

.material-icons { opacity: 0 };

I added a script tag in my index html page.

  <script>
    // This executes on chrome and EDGE only as tested
    document.fonts.onloadingdone = function (fontFaceSetEvent) {
      // console.log(fontFaceSetEvent.fontfaces) to see the available fonts which have been loaded
      // and change the font family name according to your font family requirement
      const fontName = 'Material Icons';
      if (fontFaceSetEvent.fontfaces.filter(i => i.family === fontName).length > 0) {
        addMakeIconsVisibleClass();
      }
    };

    // Fallback - call below function if not chrome (or EDGE)
    if (navigator.userAgent.toLowerCase().indexOf('chrome') === -1) {
      addMakeIconsVisibleClass();
    }

    function addMakeIconsVisibleClass() {
      let style = document.createElement('style');
      style.innerHTML = '.material-icons { opacity: 1 !important }';
      document.getElementsByTagName('head')[0].appendChild(style);
    }
  </script>
Dilshan Liyanage
  • 4,440
  • 2
  • 31
  • 33
0

We can first zero the transparency of the icons using css, then display the icons after 3 seconds using addClass.

var delayInMilliseconds = 3000;
setTimeout(function() {
var element = document.getElementById("mayicons");
  element.className +="ops1";
}, delayInMilliseconds);
.material-icons {
    width: 26px;
    height: 30px;
    overflow: hidden;
    opacity: 0;

}

.ops1{
    opacity: 1;
}
<i id="mayicons" class="material-icons ">list</i>
ABlue
  • 664
  • 6
  • 20
0

Combining several good proposals, I decided for this solution: Set display:none and replace it later with display:inline-block in the page on-load handler. To avoid layout shifts I added the known icon height, in my case height:64px.

Related style:

.material-icons-round {
    font-family: "Material Icons Round";
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: none;    /* will be replaced once font has loaded */
    transition: opacity 0.24s ease-in-out;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    width: 21px;
}

Usage (Django template):

<div class="my-card">
  <a href="#" onclick="{{ onclick }}">
    <div class="my-card-content center-align">

      <div class="my-card-title">{{ title }}</div>

      <div style="margin-bottom:8px; height:64px">
        <i class="notranslate material-icons-round medium">{{ icon }}</i>
      </div>

      <div>{{ text }}</div>
    </div>
  </a>
</div>

Page on-load handler:

window.addEventListener("load", function()
{
    // the fonts have been fully loaded, so show the icons
    const icons = document.getElementsByClassName('material-icons-round')
    Array.from(icons).forEach(icon => {
        icon.style.display = 'inline-block'
    });
});
Ramon
  • 89
  • 1
  • 3
-1

Create a div style = "position: absolute; top: -1000px" on the homepage and enter all items with class material-icon or awesome font as follows:

<div   style="position:absolute;top:-1000px" >      
<i class="icon material-icons-outlined"  >add_circle</i>
<i class="icon material-icons "  >list_alt</i>
<span class="fas fa-circle fa-stack-2x " ></span>
<span class="fas fa-home fa-stack-1x fa-inverse"  ></span>
</div>