Native Web Share API on Chrome for Android

Web Share API

Soon, there will be little need to add those bloated scripts from half a dozen different social networking sites. If you import scripts for the official Facebook, Twitter, Google+, etc. shares, consider this an “I lost over 100 lbs overnight” trick that will really work for your website.

tl;dr Click here to see the Web Share API in action.

And, what’s more, it’ll remove any concerns about theming, restrictions on styling of icons, user complaints about not supporting the service they want to share with (or alternatively having the clutter of dozens of share buttons). And if you call in the next five minutes, you can get an easy to implement and customize dose of HTML, CSS, & JavaScript that’ll make it so easy to implement, even your aunt could figure it out. No promises though.

Actually, I have take that back, since Promises, the JavaScript kind, are part of the spec. Introducing the Web Share API.

navigator.share({
	title: document.title,
	text: 'Check out Web Share API',
	url: location.href
}).then(/* Shared */).catch(/* Share canceled */);

The handler

Since I’m a real sucker for making use of the dataset API and microdata, I wrote up a few lines of JavaScript to make sharing pages or elements within a page nearly as easy as can be.

The JavaScript I use to do this is a bit lengthy since I really try to make it handle a wide variety of uses, so I’m not including it here.

  • If data-share has no value, (an empty string), share the page
  • If it is the CSS selector to a microdata / structured data element, piece together info from the embedded data
  • If it is an <img>, share that with the alt as it’s title
  • If it is an <a>, share that link with title taken from title and text taken from the link’s text content

Just create some <button>s with data-share

<button data-share="">Share Page</button>
<button data-share="article">Share Article</button>
<button data-share="#share-api-demo-target">Share Image</button>

Import and use

import {$, shareHandler} from './my-script.js';

$('[data-share]').click(shareHandler);

The Hacking for other browsers

Then, I made a polyfill of sorts for everything that’s not Chrome on Android using the <dialog> element and the URL API.

Web Share API polyfill using <dialog>
Web Share API polyfill using <dialog>

Test it out

Polyfill Gist

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Share API Shim Demo</title>
<script type="aplication/javascript" src="/path/to/dialog-shim.js"></script>
<script type="module" src="index.js" async=""></script>
<script type="application/javascript" async="" nomodule=""></script>
</head>
<body>
<button class="share">Share this page</button>
</body>
</html>
view raw index.html hosted with ❤ by GitHub
import shareShim from './share-shim.js';
import {facebook, twitter, googlePlus, linkedIn, reddit} from './share-config.js';
shareShim(facebook, twitter, linkedIn, googlePlus, reddit);
document.querySelector('.share').addEventListener('click', async () => {
try {
navigator.share({
title: document.title,
url: location.href,
text: 'Shared using `navigator.share`'
});
} catch(err) {
console.error(err);
}
});
view raw index.js hosted with ❤ by GitHub
/**
* Icons available at https://github.com/shgysk8zer0/logos
* `mkdir img`
* `git submodule add https://github.com/shgysk8zer0/logos.git img/logos`
*/
export const facebook = {
url: new URL('https://www.facebook.com/sharer/sharer.php?u&t'),
icon: new URL('/img/logos/Facebook.svg', location.origin),
text: 'Facebook',
};
export const twitter = {
url: new URL('https://twitter.com/intent/tweet/?text&url'),
icon: new URL('/img/logos/twitter.svg', location.origin),
text: 'Twitter',
};
export const googlePlus = {
url: new URL('https://plus.google.com/share/?url'),
icon: new URL('/img/logos/Google_plus.svg', location.origin),
text: 'Google+',
};
export const linkedIn = {
url: new URL('https://www.linkedin.com/shareArticle/?title&summary&url'),
icon: new URL('/img/logos/linkedin.svg', location.origin),
text: 'LinkedIn',
};
export const reddit = {
url: new URL('https://www.reddit.com/submit/?url&title'),
icon: new URL('/img/logos/Reddit.svg', location.origin),
text: 'Reddit',
};
view raw share-config.js hosted with ❤ by GitHub
export default (...shares) => {
if (! Navigator.prototype.hasOwnProperty('share')) {
Navigator.prototype.share = ({
text = null,
title = null,
url = null,
} = {}) => new Promise((resolve, reject) => {
const size = 64;
const dialog = document.createElement('dialog');
const header = document.createElement('header');
const close = document.createElement('button');
const body = document.createElement('div');
const msg = document.createElement('b');
const font = 'Roboto, Helvetica, "Sans Seriff"';
function closeDialog(event) {
if (
(event.type === 'click'
&& ! event.target.matches('dialog[open],dialog[open] *')
) || (event.type === 'keypress' && event.code === 'Escape')
) {
dialog.close('Share canceled');
}
}
function closeHandler(event) {
if (Element.prototype.hasOwnProperty('animate')) {
const rects = dialog.getClientRects()[0];
const anim = dialog.animate([
{top: 0},
{top: `-${rects.height}px`},
], {
duration: 400,
easing: 'ease-out',
fill: 'both',
});
anim.onfinish = () => dialog.remove();
} else {
dialog.remove();
}
document.removeEventListener('keypress', closeDialog);
document.removeEventListener('click', closeDialog);
reject(new DOMException(event.detail));
}
if (text === null && title === null && url === null) {
reject(new TypeError('No known share data fields supplied. If using only new fields (other than title, text and url), you must feature-detect them first.'));
} else {
msg.textContent = 'Share via';
close.title = 'Close dialog';
shares.forEach(share => {
const link = document.createElement('a');
const icon = new Image(size, size);
if (share.url.searchParams.has('url')) {
share.url.searchParams.set('url', url);
} else if (share.url.searchParams.has('u')) {
share.url.searchParams.set('u', url);
}
if (share.url.searchParams.has('title')) {
share.url.searchParams.set('title', title);
} else if (share.url.searchParams.has('t')) {
share.url.searchParams.set('t', title);
}
if (share.url.searchParams.has('text')) {
share.url.searchParams.set('text', text);
}
link.style.setProperty('display', 'inline-block');
link.style.setProperty('margin', '0.3em');
link.style.setProperty('text-decoration', 'none');
link.style.setProperty('color', '#626262');
link.style.setProperty('text-align', 'center');
link.style.setProperty('font-family', font);
link.style.setProperty('font-size', '20px');
link.target = '_blank';
icon.src = share.icon.toString();
link.href = share.url.toString();
link.title = share.text;
link.append(icon, document.createElement('br'), share.text);
body.append(link);
link.addEventListener('click', () => {
resolve();
dialog.close();
});
});
dialog.style.setProperty('display', 'block');
dialog.style.setProperty('position' ,'fixed');
dialog.style.setProperty('background', '#fefefe');
dialog.style.setProperty('top', '0');
dialog.style.setProperty('bottom', 'auto');
dialog.style.setProperty('left', '0');
dialog.style.setProperty('right', '0');
dialog.style.setProperty('transform', 'none');
dialog.style.setProperty('border-radius', '0 0 5px 5px');
dialog.style.setProperty('max-height', '500px');
header.style.setProperty('height', '47px');
header.style.setProperty('line-height', '47px');
header.style.setProperty('color', '#232323');
header.style.setProperty('box-shadow', 'none');
header.style.setProperty('border-bottom', '1px solid #d5d5d5');
msg.style.setProperty('font-family', font);
msg.style.setProperty('font-size', '24px');
msg.style.setProperty('display', 'block');
close.style.setProperty('float', 'right');
close.style.setProperty('font-family', font);
close.style.setProperty('font-weight', 'bold');
close.style.setProperty('font-size', '16px');
if (CSS.supports('width', 'fit-content')) {
dialog.style.setProperty('width', 'fit-content');
} else if (CSS.supports('width', '-moz-fit-content')) {
dialog.style.setProperty('width', '-moz-fit-content');
} else if (CSS.supports('width', '-webkit-fit-content')) {
dialog.style.setProperty('width', '-webkit-fit-content');
} else {
dialog.style.setProperty('min-width', '320px');
}
header.append(close, msg);
close.append('X');
dialog.append(header);
dialog.append(body);
document.body.append(dialog);
dialog.showModal();
if (Element.prototype.hasOwnProperty('animate')) {
const rects = dialog.getClientRects()[0];
dialog.animate([
{top: `-${rects.height}px`},
{top: 0},
], {
duration: 400,
easing: 'ease-out',
fill: 'both',
});
}
dialog.addEventListener('close', closeHandler, {once: true});
document.addEventListener('keypress', closeDialog);
document.addEventListener('click', closeDialog);
close.addEventListener('click', () => {
dialog.close('Share canceled');
}, {once: true});
}
});
}
};
view raw share-shim.js hosted with ❤ by GitHub