Skip to main content

Flutter Web Renderers (as of Flutter 3.10)

· 15 min read
Sai Rajendra Immadi

Are you always confused why your Flutter app on the web gives out some lags, errors or sometimes with even no proper colors on images? Think no further to read this blog to learn everything about Flutter Web that can help you solve your problems or understand Flutter Web better for your new/existing web application.

Flutter Web Renderers Thumbnail

Renderer

A renderer is something under the hood that a framework does (flutter here) to render the web application. In Flutter 3.10 (stable channel), the dart code is compiled to js using dart2js so a web browser can interpret it.

I won't go deep into how rendering works, but here is a small story you need to know. Flutter engine, written in C++, web doesn't have access to the underlying engine, unlike mobile and desktop. With this, Fluter needed a different approach to render on the web. With the need for a different approach, Flutter had to reimplement the engine on top of browser-specific API only for the web.

Types of renderers

The first approach for Flutter was to use HTML elements, CSS and Canvas API. Here the Flutter engine creates HTML elements and then puts out the styling and arrangement using the Canvas API. This renderer is named HTML.

The second approach for Flutter is to bring the SKIA (graphics engine) compiled to Web Assembly using the Canvas Kit. Here the Flutter engine creates Canvas elements and uses the Canvas Kit API to style the canvas. This method brings more control and flexibility as the Flutter engine uses SKIA (slowly moving to Impeller on mobile) to render. This renderer is named CanvasKit.

Flutter web architectural layer

Flutter web architectural layer

Development

Flutter is known for its hot reload feature. But, with the different approach needed for Flutter Web, we don't get to experience this feature. So, to solve this, Flutter uses dartdevc that supports an incremental compilation during the development process and supports hot restart (not hot reload). It uses the dart2js when creating production apps.

Use Renderer

You can use the --web-renderer command line option:

  • auto (default)
  • html
  • canvaskit

By default, Flutter uses auto (no need to specify anything). It uses the html renderer on mobile and canvaskit renderer on desktop browsers.

Example usage for the run or build subcommands:

flutter run -d chrome --web-renderer html
flutter build web --web-renderer canvaskit

There is also an option to override the renderer at run-time.

web/index.html
<body>
<script>
let useHtml = true;

window.addEventListener('load', function(ev) {
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
// Run-time engine configuration
let config = {
renderer: useHtml ? "html" : "canvaskit",
};
engineInitializer.initializeEngine(config).then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body>

To learn more about the usage of renderers, visit Flutter docs.

Pros and Cons

Everything has advantages and disadvantages. Let us look at what they are in different renderers.

HTML renderer

This renderer uses a combination of HTML Elements, CSS, Canvas Elements & SVG Elements to render the web page. It is pretty straightforward (as mentioned above) in how it lays the components.

+'s:

  • Have a lesser bundle size
  • Faster initialization
  • Use native text rendering

-'s:

  • Problematic SVG support
  • Few Canvas APIs don't work properly

CanvasKit renderer

This renderer (as mentioned above) uses CanvasKit to render the web page. CanvasKit uses WebGL, a graphics API, to execute SKIA paint commands. It is on par with the Flutter mobile and desktop applications.

+'s:

  • Faster performance
  • Higher widget density
  • Browser independent

-'s:

  • Adds 1.5MB of bundle size
  • Doesn't use native text rendering
  • CORS issues

In Detail

We've learnt about the renderers in general. Now, it's time to dive into various topics related to renderers.

Simple Rendering

Let us consider a sample code that consists of only texts to look at the difference in the rendering and their elements.

Example code for simple rendering.
Column(
children: const [
Text('This is center aligned'),
Align(
alignment: Alignment.topLeft,
child: Text('This is left aligned'),
),
Align(
alignment: Alignment.topRight,
child: Text('This is right aligned'),
),
Padding(
padding: EdgeInsets.only(left: 10),
child: Text('This is center +10 aligned'),
),
Padding(
padding: EdgeInsets.only(right: 10),
child: Text('This is center -10 aligned'),
),
],
),

Output for the above code would be this (below image) for any renderer:

Simple rendering output

But we will find the actual difference when inspecting the page. It is as follows:

Simple rendering html 1Simple rendering html 2
Simple rendering inspect - html
Simple rendering canvaskit
Simple rendering inspect - canvaskit

Looking at the HTML elements from both renderers, it is clear that the HTML renderer uses different HTML and Canvas elements to render the widgets, while the CanvasKit renderer uses only one Canvas element and paints the page using APIs. This technique makes the CanvasKit renderer perform better, be consistent with mobile and desktop and make it browser independent (as it just paints and does not depend on browser compatibility of elements).

CanvasKit, to be able to use the CanvasAPI and WebGL, has to ship a bit more files than HTML, which makes the web built using HTML renderer load faster. The difference in default download size is 1.5MB more in CanvasKit render. It was 2MB before, which tells us that the Flutter team is working hard on improving things daily.

Wow!! With just one section, we covered most of the pros and cons of both renderers. Let's learn about the remaining things left.

Native Text Rendering

I used this term above, mentioning it is an advantage in HTML rendering and a disadvantage in CanvasKit rendering. To experiment on this, let us consider an example:

Example code for native text rendering.
Center(
child: Text('Using Emoji - 🫨'),
),

Time to render this sample code in both renderers, starting with the HTML renderer.

Native text rendering html output
Native text rendering - html renderer output

There is no problem here, and the output is as expected. So, why bother? Let's look at how the CanvasKit renderer renders for the answer.

Native text rendering canvaskit output
Native text rendering - canvaskit renderer output

Why did we go back in time (black and white emoji)? You might now start to think "native text rendering" definitely is a disadvantage in the CanvasKit renderer. But why and how?

Let us look back on what we learned about how HTML renders. It uses HTML elements, including the text. It makes the text access the default browser/platform-supported fonts. This process of using native fonts is what we call "Native Text Rendering". But why does the CanvasKit renderer not able to access native fonts? Again, when you look back on how CanvasKit renders the page. It has only one Canvas element and paints appropriately using the APIs. The bundle in CanvasKit doesn't know the fonts (emojis included), painting only in black and white.

Solution 1

With the new Flutter 3.10, there is a solution to solve this by using Color Emoji with configuring the Flutter engine.

web/index.html
let config = {
// uses Color Emoji font
useColorEmoji: true,
};
engineInitializer.initializeEngine(config);

The output after this change when using the CnavasKit renderer for this would be as below:

Native text rendering canvaskit with color emoji output
Native text rendering with useColorEmoji - canvaskit renderer output

Well, I could say it didn't solve my problem. As you can see in the output from the HTML renderer, it uses the emoji from Apple. But, this solution only gave me the Google Color Emoji font.

Solution 2

With the limitation in the previous solution, there is a new page for another solution. It is to ship the font in addition to the already existing bundle. But remember, this solution increases the download size (apart from the 1.5MB).

Add the font file as an asset to your project, say fonts/AppleColorEmoji.ttf. Then add it to your pubspec.yaml file:

pubspec.yaml
flutter:
fonts:
- family: AppleColorEmoji
fonts:
- asset: fonts/AppleColorEmoji.ttf

Now change the code to of your Text widget to include the font:

Upgraded example code for native text rendering.
Center(
child: Text(
'Using Emoji - 🫨',
style: TextStyle(fontFamily: 'AppleColorEmoji'),
),
),

Now looking at the output after the change to include the font file directly into the bundle, we get the desired result with the CanvasKit renderer.

Native text rendering canvaskit with apple font output
Native text rendering with font file - canvaskit renderer output
tip

You could use the RichText (or Text.rich) and TextSpan widgets to use different fonts in the same sentence.

Phew! That was a very bumpy ride. It is a widely known issue in the Flutter community for no reason. To learn more on this issue, visit here.

Problematic SVG support

We know there won't be any more problems with the CanvasKit renderer. The problem here is with the HTML renderer. Considering a simple example displaying an SVG image:

Example for svg image.
SvgPicture.asset('assets/Firefox.svg'),
note

SvgPicture is a widget from the flutter_svg package.

Comparing the result from both renderers:

Output of problematic svg with html renderer
Firefox SVG - html
Output of problematic svg with canvaskit renderer
Firefox SVG - canvaskit

Our assumption about the result from both renderers is True. Here in this example, the gradient color in the HTML renderer is different due to some problems with the SVG support in the HTML renderer. This current problem is with the two-point canonical gradient not being supported.

To learn more about this problem and other problems with HTML rendering, start with this issue.

Canvas API limitations

Unlike the previous problems with SVG support in HTML renderer, this is another issue with a limitation from using Canvas.saveLayer. It closes the door for different uses, like blending colours, etc.

I will show you a simple example of how it affects blending in this image. For more information, visit the Flutter issue.

Example of color blending with SVG.
SvgPicture.asset(
'assets/flutter_logo.svg',
colorFilter: const ColorFilter.mode(Colors.blue, BlendMode.difference),
),

Output for these would be:

Output of blending a flutter logo with html renderer
Flutter SVG blend - html
Output of blending a flutter logo with canvaskit renderer
Flutter SVG blend - canvaskit

Now that we are in the concept of Images. Let us look at what they are in Flutter web.

Images in Flutter Web

On the web, Flutter uses three different methods to display images:

  • <img> and <picture> - built-in HTML elements
    • Has browser caching, image optimization and memory management
  • drawImage on <canvas> - Canvas element
    • Able to size images and read pixels for further processing
  • A custom codec that renders to WebGL canvas
    • Can apply custom algorithms to pixels, use GLSL (shaders)

The HTML renderer uses the first two, and the CanvasKit uses the third method from the above three methods.

Because of the usage of the <img> element by the HTML renderer, it can display images from arbitrary sources. It places some restrictions on what a renderer can do with the Image. Learn more about those limitations from Flutter docs.

CanvasKit, to apply custom image manipulation, need full access to the pixels of the Images. To do so, it is subject to CORS policy.

Cross-Origin Resource Sharing (CORS)

CORS is a mechanism that allows a server to indicate any origins other than its own from which a browser should permit loading resources.

When getting images from different sources (arbitrary sources) into the elements <img>, <picture> and <canvas>, the browser automatically blocks access to the pixel data, and the CORS policy disallows the data. So, the HTML renderer doesn't have issues with the CORS policy apart from a few limitations.

CanvasKit renderer needs access to pixels to paint them. So, it needs images to follow CORS policy. And which don't follow the CORS policy (from an arbitrary source) are not displayed on the web page.

Let us look at it in action:

Example for CORS policy.
Image.network('https://somesite.com/someimage.jpg'),

Output:

Arbitrary source image with html renderer
Arbitrary source image - html renderer
Arbitrary source image with canvaskit renderer
Arbitrary source image - canvaskit renderer

Seeing this, we learned that to use images from arbitrary sources with CanvasKit renderer following CORS policy is a must. Else, it would throw an error (like above).

What could you do to solve this issue if you want to use an image from an unknown source?

Solution

There are different methods to solve this issue:

  • Make the Image an asset
    • It becomes the same-origin as the server following the CORS policy
  • Host the Image in CORS-enabled CDN
    • Content delivery network - configure what domains can access the Image
  • Use CORS Proxy
    • If there is no control of the Image server, use a proxy to load the Images

To view more in detail on how to use these solutions and how images work with Flutter web, you can go through the Flutter docs.

What to Choose?

You have told us a lot!! Now, what renderer do I choose? The reason two renderers are present is for different use cases. So, it depends on what kind of application you are building. After reading this (if you have read it), you can easily decide what renderer you need.

To help you, I have curated a few points on when to use what.

What renderer to choose for flutter web
Classification on what flutter web renderer to choose

Future - WASM

The future of Flutter web is getting the WebAssembly (Wasm) directly instead of going through the dart2js (eliminating JavaScript). It would be dart2wasm.

The Flutter and Dart team are already working on compiling directly to Wasm instead of Js. And they are almost near getting it out to the public (stable release). With the latest announcement of Dart 3, the compatibility for Wasm is not so far. And we also get to experience the first preview of Wasm in the master channel of Flutter.

Flutter currently already uses Wasm in the stable channel. Not compiling dart code to Wasm but instead converts the C++ code of CanvasKit (SKIA) to Wasm.

As of today (the time of writing this blog), the Build time of the Flutter web with Wasm (master) is slower. But, we can see a smaller download size and faster web application (but with some limitations). You can view the preview app from the release here.

Learn more about Wasm in Flutter by visiting here and watching this video.

In short, it would be a big jump for Flutter on the web once the work with Wasm becomes stable. Performance, Size would be huge factors here.

Conclusion

Flutter Web is a vast topic to discuss or ponder upon. There are topics like Fragment Shaders, Element Embedding and many other things. I only choose the concept of renderers as a start because I feel it is a fundamental basic concept that developers need before building web applications in Flutter. It is to provide the current problems/limitations with Flutter Web and to understand different topics related to renderers in Flutter that motivated me to write this blog.


If this has been useful, please consider sponsoring me via

This post is licensed under  CC BY 4.0  by the author.