14

I have an ongoing problem with custom webfonts and fabric.js. My app uses a lot of custom webfonts and I init them when adding an iText to my canvas:

var text = new fabric.IText("My Text", {
    fontFamily: "Some Custom Font Family",
    fontSize: 50,
    top: 0,
    left: 0,
    fill: "#000000"
  });

  canvas.add(text);
  canvas.bringToFront(text);
  canvas.setActiveObject(text);

  canvas.renderAll();

That works but only if I click on the iText on my canvas and interact with it. Then, once the font has loaded, its not a problem anymore. The problem is initially and the first time the iText is added.

I researched a lot and came to this thread:

Init loaded text with remote web font in Fabric.js

but that didn't help me. The jsfiddle provided there has the exact same problem:

http://jsfiddle.net/vvL6f/6/

Just open this fiddle with a fresh browser (e.g. Chrome CMD+Shift+R) with cleared cache. You will see once you open the fiddle, the custom webfont is not loaded but immediately loads when you click on the iText on the right.

Now, how can we solve this?

A suggested approach was to set useNative to false and let Cufon render the text, but that didn't work.

I load my webfonts in a CSS file like this:

@font-face {
    font-family: 'ApolloASM';
    src: url('ApolloASM-webfont.eot');
    src: url('ApolloASM-webfont.eot?#iefix') format('embedded-opentype'),
         url('ApolloASM-webfont.woff2') format('woff2'),
         url('ApolloASM-webfont.woff') format('woff'),
         url('ApolloASM-webfont.ttf') format('truetype'),
         url('ApolloASM-webfont.svg#apollo_asmregular') format('svg');
    font-weight: normal;
    font-style: normal;
}
Community
  • 1
  • 1
DonMB
  • 2,550
  • 3
  • 28
  • 59
  • Ýou solved this problem? – ptCoder Mar 19 '15 at 18:38
  • no, unfortunately not :-( – DonMB Mar 19 '15 at 19:08
  • Check this: http://www.atomicjetpacks.com/blog/8/how-to-detect-if-a-webfont-really-loaded-ii - https://github.com/JenniferSimonds/FontDetect And here you have explanation of it works: http://stackoverflow.com/a/12316349/2247124 – ptCoder Mar 19 '15 at 19:25
  • I'm not sure why, but for me it works by requesting `https://fonts.googleapis.com/css?family=Font` instead of `https://fonts.googleapis.com/css?family=Font:100, 200,400,900`, my guess is that works because of less loading time. "fast enough" to load before `canvas` – Horacio Apr 12 '16 at 01:49

5 Answers5

16

@Prominic answer is almost there. I also have a canvas app which uses custom fonts with Fabric. I load all fonts in a CSS just as you do. The catch is, only declaring the @font-face in your stylesheet is not enough. The browser won't request the font file unless it is used in the document. And you have to be sure to load and apply the webfont before initializing the canvas. This can be achievable with a little bit of work, which I'll illustrate with an example below. The downside is that you might need to preload all fonts that your app make available to your users.


  1. Declare your webfont

    Add a @font-face statement in a stylesheet. Just make sure it is loaded in the <head> section. Let's use your example:
 @font-face {
    font-family: 'ApolloASM';
    src: url('ApolloASM-webfont.eot');
    src: url('ApolloASM-webfont.eot?#iefix') format('embedded-opentype'),
         url('ApolloASM-webfont.woff2') format('woff2'),
         url('ApolloASM-webfont.woff') format('woff'),
         url('ApolloASM-webfont.ttf') format('truetype'),
         url('ApolloASM-webfont.svg#apollo_asmregular') format('svg');
    font-weight: normal;
    font-style: normal;
}

Bonus Tip: If you use italic and/or bold in your app, is adviseable to add the font-faces for those styles as weel if you want consistent results across different browsers. For example, Android Browser 4.2 can't compute italic of a font, and will display it as normal.

  1. Force the request for the font file

As I said above, you will need to actually use the font in the DOM to force the font to load. Just adding a div with some text and styling it with your font will be enough:

<div style="font-family:'ApolloASM'">&nbsp;</div>

Bonus Tip: Don't forget to add the italic and bold variations as well

  1. Init the FabricJS Canvas after the DOM load event

You will need to sync the canvas init (or at least the text addition) after the font is loaded by the browser, or else you might see a FOUT effect (Flash of Unstyled Text). Luckily for us, the browser won't fire the load event until all font files are loaded, which can be used to safely add a styled text to canvas:

window.addEventListener('load', function onLoad() {
    var canvas = new Fabric.Canvas({...});
    var text = new fabric.Text("Web Font Example", {
        left: 200,
        top: 30,
        fontFamily: 'ApolloASM',
        fill: '#000',
        fontSize: 60
    });

    canvas.add(text);
    canvas.renderAll();
})

Bonus Tip: You can wrap those fonts <div>'s in another one and remove it after the DOM load event, to avoid breaking any layout you might have.

4

You could use the web font loader.

    <script src="//ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js">    </script>
    <script>
    var c = new fabric.Canvas('canvas');
    WebFont.load({
     google: {
      families: ['Fredoka One']
     },
     active: function() {
            var text = new fabric.IText('Hello world !', {
                fontFamily: 'Fredoka One',
                top: 100,
                left: 100,
                fontSize: 20
            });
         c.add(text);
     },
    });
    </script>

JSFiddle here.

skuntsel
  • 11,624
  • 11
  • 44
  • 67
Naoyuki
  • 41
  • 4
1

What is happening is the webfont hasn't been loaded yet when your script first renders the canvas.

I'd say the best way to execute this is to load the webfont using an HTML tag in the . This will load the font at the top of the DOM so that the font is ready for your canvas scripts when the browsers renders it. Here is an example.

https://jsfiddle.net/j47k79e8/1/

<link href='http://fonts.googleapis.com/css?family=Ranchers' rel='stylesheet' type='text/css'>

Though if you still wanted to use the Javascript method to load the webfont, you can wait for the document to load before running your FabricJS scripts. Just wrap it in a document ready statement.

$(document).ready(function(){
  // FabricJS scripts
});

https://jsfiddle.net/ahxpeu0v/3/

PromInc
  • 1,174
  • 7
  • 12
  • Well, I load my fonts already in the header via its own css. They should also get initialised properly. See OP, just edited. Wrapping it in a document ready statement won't work as I call the addText function on demand. – DonMB Mar 08 '15 at 19:37
  • It looks like your fonts are self hosted? That was another thought I had... try to reduce as many latency variables as possible. My other thought would be to try a different font. The goal there is to merely find where the issue might be - could be something unique to that specific font? All looks correct to me. I haven't worked with IText much yet though, so maybe there is something unique to how IText loads custom fonts. – PromInc Mar 10 '15 at 13:26
0

You only need to put the canvas.renderAll(); command after inserting the text, like this:

canvas = new fabric.Canvas('c'); 
var text = new fabric.Text("Web Font Example", {
    left: 200,
    top: 30,
    fontFamily: 'Ranchers',
    fill: '#000',
    fontSize: 60
});

canvas.add(text);
canvas.renderAll();

Fiddle: http://jsfiddle.net/vvL6f/107/

ptCoder
  • 2,229
  • 3
  • 24
  • 38
  • It doesn't work when you first load it, probably because the font isn't cached yet. – Shomz Mar 05 '15 at 13:55
  • I do that - see my original Post. @Shomz yes this is also what I suspect but I don't know how to solve it in this context. – DonMB Mar 05 '15 at 14:32
  • @DonMB, I've tried a few things, but in the end deleted my answer because all version suffered from the same problem one way or another. And I refuse to use timeouts. Someone will probably come up with something nice, just be patient. – Shomz Mar 05 '15 at 14:59
  • Thanks for your efforts. Yes I also thought about timeouts but that would only slow down my app - no good. – DonMB Mar 05 '15 at 20:03
0

Answer by @Bernardo Domingues was not good for me because I have <select> with more than 500 fonts and I do not want to preload all of them due to traffic constraints.

So I kinda solved this problem by repeatedly calling Canvas.renderAll() method every 500ms. Again and again. This way after I change a font to a new one (that is not loaded by browser yet) - first it will be downloaded by browser (I do not know and I do not care how much time will it take). As soon as new font will be loaded - my code will update a whole canvas in 500ms and voila! my fonts are updated also :)

yaru
  • 1,260
  • 1
  • 13
  • 29