Embedding Complex SVGs Into HTML
(AKA Crazy shit we did to make SVG work for us)
Cameron Lakenen – Box
http://camupod.com/html5devconf2013
Cameron Lakenen
Engineer at Box
Preview and View API on document viewing stratgey in the brwoser
aventures in SVG→
The Box View API (formerly Crocodoc) is a service for generating portable, web-viewable versions of documents
Documents are converted into HTML5 and viewed in the browser using Viewer.js
Preview and View API use Crocodoc
world-class document viewer →
[scrolling] Here's an example of the document viewer we've built →
We combine three web standards to render documents:
rendering strategy is based on HTML5 →
CSS →
and SVG →
Why SVG?
*Can't you do everything with HTML + CSS?*
HTML5 is great
aside from canvas it's not enough →
why SVG? →
provides some operations not available
clipping, masking, blending, paths →
web standards, *just work*, portable
text file* compresses, important for mobile →
infinite zooming →
Strokes and Fills
in addition to images and text
draw paths, stokes and fills →
Clipping, Masking, and Blending
complex graphics operations
clipping, masking, blending transparency groups →
Rendering Quality and Zooming
Vector graphics scale infinitely (excluding rasterized images)
zooming
"scalable vector graphics"
rasterized images
crisp zooming →
So, what about canvas? →
interesting option, more complex to render →
requires JS →
zooming or resizing requires a redraw in JS
interesting projects, PDF.js →
SVG yielded interesting problems, embedding in HTML
several ways to embed →
* basic img tag or css background-image →
* inline svg
* "true" inline vs DOMParser →
* object, iframe, and embed tags →
different implications →
* performance and stability
* jank-free scrolling and zooming
* mobile stability →
* ability to load external assets or modify SVG content on the fly
* I'll explain why this is necessary →
* native browser loading indicator spinning on every emebd →
biggest issue is performance
Some docs very complex => complex SVG
conversion process to reduce complexity
* file size and rendering performance
affecting rendering quality. →
lazy-load pages => faster, responsive UX
embedding SVG on the fly => janky
certain methods yield better UI perf →
Memory useage, crashing mobile Safari
not going to dive too deeply into mobile →
perf: common external resources (fonts, styles) →
re-using resources like fonts across svg files
base64 encode these assets into SVG file (we do somewhat)
fonts commonly reused
encoded into each svg file, payload size++ →
modify SVG content before embedding
queryString params for A & A →
The Dreaded Spinner
screencast: doc viewer using embed method that causes spinner
annoying and jarring for the users →
Embed Methods
inline SVG (true inline vs DOMParser)
So let's talk about the different embed methods! →
First, let's look at the img tag →
<img> tag: no external assets
SVGs loaded via <img>
won't fetch external assets
https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_as_an_Image#Restrictions
Unfortunately, no external assets (security) →
<img> tag: no external assets
Solution: base64-encode all assets into nested data: urls
Very complex
Memory issues and crashing on mobile devices
solution: download with JS
base64 encode into nested data url →
very complex, issues in some browsers
it could be explored further, but probably not worth it →
Embed methods
<img> is difficult at best – let's look at our other options:
img won't work... other options? →
Inline SVG
Inline SVG is part of the [HTML 5 spec](http://www.w3.org/html/wg/drafts/html/master/single-page.html#svg)!
IE 9+
Inline SVG promising
part of HTML5 spec
works in all modern browsers, IE 9+ →
DOMParser !== inline SVG*
Inline SVG (HTML, SVG text parsed on page load) is very fast
Document viewer - pages are loaded dynamically
SVG embedded with JS is not parsed asynchronously (yet**)
*at least not in Chrome/Blink, and likely not in any browsers currently
**http://crbug.com/308321 and http://crbug.com/308768
no true inline svg in JS
viewer.js - pages are loaded dynamically
possible to load a 1000+ page document performantly
true inline SVG (literally HTML+SVG text, parsed at page load) is very fast
dynamically embed with JS requires DOMParser
DOMParser bypasses the threaded html parser => synchronous parse of the SVG content
threaded html parser is not exposed to JS, →
could change soon - two open chrome issues →
Inline (DOMParser)
This is an example document viewer that uses DOMParser to insert SVG into the DOM inline.
[scrolling] You might not be able to tell, but I'm scrolling right now... performance is pretty bad
Iframe
This is the same document, but here the SVGs are embedded as iframes.
[scrolling] As you can see, performance is much better
Iframe VS Inline (DOMParser)
Reload iframe
Reload inline
Reload both
iframe and DOMParser example side-by-side
reload the example, you can see the differences
DOMParser takes the synchronous codepath: parsed much slower than
the iframe method: native async HTML parser →
Embed methods
Inline SVG performance isn't quite there yet
inline is promising, but not where we need it yet → →
Iframe vs Object vs Embed
Effectively the same thing in most browsers
iframe, object, and embed are roughly equivalent when it comes to embedding SVG
Firefox seems happier with object tags, but otherwise we just use iframe →
## Basic embed via iframe/object
* Spinner :(
* Can't modify text before loading
```html
<iframe type="text/svg+xml" src="page-1.svg"></iframe>
<object type="text/svg+xml" data="page-1.svg"></object>
```
embedding with iframe or object tags *directly* doesn't solve our problems, although performance is great. →
still a spinner every time you load a page →
can't modify content before it's loaded
object requests the svg file directly →
Proxy-SVG
Embed object as a tiny SVG that contains a bit of JS
// proxy-svg.js
function proxyScript() {
/* proxy JS code */
}
var SVG = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg">' +
'<script><![CDATA[(' + proxyScript.toString() + ')()]]></script>' +
'</svg>';
var object = document.createElement('object');
object.type = 'image/svg+xml';
object.data = 'data:image/svg+xml;base64,' + window.btoa(SVG);
first attempt at an iframe/object-based embed strategy that solves spinner and modify SVG content before it's loaded
most complicated solution first, but interesting
downloading SVG text via AJAX, modify the content, and pass to a child object that injects the content into itself
here are some illustrations, explain simplified version →
Proxy-SVG
viewer.js in its nice little Chrome tab
assume already downloaded SVG content, and modified it as necessary, ready to embed the SVG →
Proxy-SVG
The parent window embeds a child object via base64-encoded data:url of the proxy-svg script
Proxy-SVG
The proxy-svg script runs inside the newly created object, using the postMessage API to send a mesage to the parent window, which alerts the viewer that it has loaded and is ready to accept SVG content
Proxy-SVG
The parent window receives the message, and sends a message containing the SVG content back to the object
Proxy-SVG
The object embeds the SVG content directly into its documentElement using DOMParser and importNode
Proxy-SVG
After the SVG content is embedded, proxy-svg sends a message back to the parent window to say that it's finished loading!
## Proxy-SVG
* Too complicated
* Doesn't work in IE (no scripts in data:urls)
Proxy-svg was a very interesting solution →
overly complex →
no internet explorer, no scripts in data:urls
Once we finally needed to support internet explorer, we searched for other options →
## document.write()
* Create an empty `<iframe src="">`
* `document.write()` writes SVG directly into iframe
Our next solution was much simpler. →
create an empty iframe with empty src or about:blank
considered same domain, you can interact directly with the iframe's contentWindow without worrying about the browser's security policy →
call document.write() from the parent window, and write the SVG content directly into the iframe →
## document.write()
* Works very well in Chrome and IE
* Works in FF/Safari, minus `<defs>` bug
* Causes spinner in FF
works well for the most part in all browser →
but there were some unfortunate bugs in Firefox and Safari. →
Namely, anything in the `<defs>` tag seems to be ignored by the browser.
Referencing the images directly fixes the problem, but we put our images in the defs tag so they can be reused throughout the page (which is often the case), so the defs tag is very important →
Also, document.write() causes a spinner in Firefox, which, of course, is unacceptable. →
"Direct Proxy"
Combination of Proxy-SVG and document.write() methods
Safari - create an iframe, write a script with document.write()
Firefox - create an object with data:url encoded script
Call script directly from parent window (viewer.js)
in order to get around the bugs in Firefox and Safari
yet another solution, "direct proxy" →
combines proxy-svg and document.write() strategies
initialize empty object or iframe with a simple script (similar to proxy-svg) →
object is embeded so browser's security policy doesn't interfere with direct frame-to-frame communication →
embedded script loadSVG takes an SVG string and embeds it into the frame's documentElement with DOMParser →
## "Direct Proxy"
This is what the embedded "proxy" script looks like
gets strigified into a script tag
exposes a loadSVG method that parses and embeds content →
## "Direct Proxy" - Firefox
In firefox, since the browser's security policy considers data:urls the same domain as the parent, we can simply encode the proxy script into a data:url, embed it as an object, then call the method directly
## "Direct Proxy" - Safari
In other browsers, we can use a similar method to document.write() the script into an iframe, and call the method directly on the iframe's contentWindow
## "Direct Proxy"
* Solves the `<defs>` bug in FF/Safari
* No spinner in Firefox!
The direct proxy method, although slightly more complex (and possibly less performant) than simple document.write(), seems to solve our problems in Safari and Firefox.
# Questions?
Anyways, thanks for sitting through what was basically a long rant about my adventures in SVG. Please feel free to ask me any questions, and I'll do my best to answer them!