javascriptcssmaterial-icons

Material Icons font family initially renders as text


I'm working in javascript and am applying the Material Icons fontFamily to my component. On the initial render, the child component is being rendered as normal text using what I would assume is the browser's default fallback font. Once the font has loaded, then it re-renders as an icon. I want to set this up so that nothing is displayed at all until the font has been loaded. I have normal text to the right of the icon that makes leaving the text present for accessibility an invalid argument.

Here are a couple of screenshot for reference:

Here is the code as it is now, where iconName is the text being used to render my icon:

                        <div
                          className="text-left d-flex align-items-center"
                          style={{
                            fontFamily: "Material Icons",
                            fontSize: "24px",
                            opacity: parentActive || parentExpanded ? 1 : 0.8,
                            padding: "0 9px",
                            maxWidth: "45px",
                            overflow: "hidden",
                            height: "2.5rem",
                            color:
                              parentActive || parentExpanded
                                ? color.primary
                                : undefined,
                          }}
                        >
                          <span aria-hidden>{iconName}</span>
                        </div>

I have tried a few different solutions but only one them has sort of worked and am looking for a better alternative if possible. One solution that I tried was to use a ternary operation to render a blank space when the font is not yet loaded:

                        {document.fonts.check('1em "Material Icons"') ? 
                        <div
                          className="text-left d-flex align-items-center"
                          style={{
                            fontFamily: "Material Icons",
                            fontSize: "24px",
                            opacity: parentActive || parentExpanded ? 1 : 0.8,
                            padding: "0 9px",
                            maxWidth: "45px",
                            overflow: "hidden",
                            height: "2.5rem",
                            color:
                              parentActive || parentExpanded
                                ? color.primary
                                : undefined,
                          }}
                        >
                          <span aria-hidden>{iconName}</span>
                        </div>
                        : <div style={{ width: "42px", height: "40px", color: "transparent" }}/>
                      }

This solution does work, but there is an issue with that I would like to avoid. With my original code, once the font has been loaded and you reload the page manually, the icon is loaded initially since the font has already loaded. With this new code snippet, it does avoid rendering the text when the font family has not yet been loaded at the initial render. The issue now is that it always initializes to false for some amount of time before seeing that the font is there. Running this code next to the original code at the same time shows that the icon can sometimes be rendered right away whereas this conditional statement doesn't allow that to happen.
Another idea that I am aware of is using the font-display property and setting it to block. I think this would work, but I can't figure out how to apply it to my code. It seems that webpack in my project is not configured to be able to read @font-face from a css file and I haven't seen any other way to do this. I also am not sure what to put for the font-family src since I didn't need to specify the src in the div styles before. I put questions marks in here but when I tried it I just removed the line entirely.

   @font-face {       
        font-family: 'Material Icons';
        src: ??;
        font-display: block;   
    }      
   .material-icons {       
        font-family: 'Material Icons', sans-serif;     
   } 

I get an error in the browser when I try to do this:
ERROR in ./src/navigation/components/iconFont.css 1:0 Module parse failed: Unexpected character '@' (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders > @font-face { | font-family: 'Material Icons';

I don't want to have to update the project configurations to get that approach to work. Does anyone have any ideas that I could try out? Any help is much appreciated!


Solution

  • This is an intentional behaviour for multiple reasons:

    However, if you really want to customise this behaviour, you can try one of the following:

    Use codepoint instead of ligature

    Instead of using ligature like face, use &#xE87C; instead (which is a codepoint encoded in HTML). This can prevent rendering the text completely but you will still see an empty box like this when the font haven't loaded:

    You can read more about it here: https://developers.google.com/fonts/docs/material_icons/

    And find the mapping between ligature and unicode codepoint here: https://github.com/google/material-design-icons/blob/master/font/MaterialIcons-Regular.codepoints

    Override the font face in your HTML

    As mentioned in your question, adding font-display: block is another way to completely not rendering anything until the font is loaded. This is called as preventing Flash of unstyled text (FOUT)

    However, without your webpack / build tools configuration, we have no way to tell which part of your loaders are not configured correctly.

    A quick fix would be modify your index.html (the template of your React app) and load your font there. Assuming you are using Google Fonts, you will need to add the &display=block parameter like the following:

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