Sharing Images, Canvas and HTML using the Web Share API
TLDR; here is the demo + repo of the code snippets in this blog post.
Recently when working on a silly side project, my friend Eden suggested that I add sharing functionality. However my web application was just a HTML page with a bunch of images on it, it wasn't on a canvas element, I thought surely it wouldn't be (easily) achievable to share the contents of that page. This sent me down a rabbit hole of what is possible for sharing on the web.
What can you share?
If you read through the documentation on the web share API, it mainly focusses on sharing links and text, with a brief touch on "files". Most documentation doesn't drill into how you would get the image file contents and actually share that using the web share API. This blog post will dive into how you can share not only an image file, but how you can share canvas contents and even arbitrary HTML elements.
What platforms does this work on?
For a thorough breakdown see the caniuse page. Basically at the time of writing Android and iOS are the major platforms supported, with Chrome on Windows and Chrome OS also working. I've tested on Windows (Chrome + Edge) and Android (Chrome) myself. The share dialogs work as expected when sharing an image. Obviously Windows share API is a little more useless than Android, but it still works okay. Hopefully with the ability for progressive web applications to present themselves as share targets, more sharing options will be possible.
Sharing an Image
Here is the basic snippet for how you can share an image from a certain URL. You can see it in practice on this demo site.
async function shareImage() {
const response = await fetch('nacho.jpg');
const blob = await response.blob();
const filesArray = [
new File(
[blob],
'meme.jpg',
{
type: "image/jpeg",
lastModified: new Date().getTime()
}
)
];
const shareData = {
files: filesArray,
};
navigator.share(shareData);
}
What we are doing here is:
- Making a request for an image from a server
- Extracting it's raw contents as a blob
- Creating a File object from the blob
- Sharing the created file via the web share API
To make your code more resilliant to cases where the feature is unavailable, you can also check for if the sharing API is available:
if (navigator.canShare && navigator.canShare(shareData)) {
... share
} else {
// do something else
}
Sharing a Canvas
The steps for sharing a canvas are almost identical as for sharing an image, except that instead of pulling the image contents from a fetch request, we're extracting it from the canvas.
Here is the code snippet, you can see it in practice with an animated canvas on my demo site.
async function shareCanvas() {
const canvasElement = document.getElementById('mycanvasid');
const dataUrl = canvasElement.toDataURL();
const blob = await (await fetch(dataUrl)).blob();
const filesArray = [
new File(
[blob],
'animation.png',
{
type: blob.type,
lastModified: new Date().getTime()
}
)
];
const shareData = {
files: filesArray,
};
navigator.share(shareData);
}
The main things to callout here, is that we are using toDataURL()
to get the data url representation of the canvas, then using a neat trick of passing that data url to a fetch to convert it into a blob.
Sharing arbitrary HTML in the DOM?
So at first this seems a little wild, I didn't know how on earth it would work. But thanks to the magic of a library called html2canvas, it's possible to also share sections of your HTML. There are some limitations around cross origin images, but works perfectly fine if all the content is under your control.
The code snippet is the same as the above canvas example but with an extra step at the start to convert the HTML to a canvas element. Here is the snippet in practice on my demo site.
const canvas = await html2canvas(shareTarget.current);
How does it work in practice?
I took the above learnings and added the sharing functionality to my silly side project, you can see the "Share" button at the bottom of the rating pages. Here is the result of the shared contents.