Sharing in the Age of 3p Cookie-mageddon
Over a decade ago, I wrote up detailed instructions on how to enable users to share your content on social media without allowing them to be tracked by every social media site via cookies. In a few short weeks “third party” cookies will get the boot in Chromium-based browsers. If you’re still relying on third party share widgets on your site, your users may start seeing problems. Now is a good time to replace them with code that Just Works™. Here’s how…
Sharing, the Old-fashioned Way
When it comes to sharing, there are myriad ways to do it. If you’re at all familiar with my work, it should come as no surprise that I always start with a universally-useable and accessible baseline and then progressively enhance things from there. Thankfully, every social media site I commonly use (with the exception of the Fediverse) makes this pretty easy by providing a form that accepts inbound content via the query string.1 You can try LinkedIn’s to see it in action.
Each service is a little different, but all function similarly. I support the following ones in this site:
Site | Destination | URL | Optional Params |
---|---|---|---|
Twitter / X | https://twitter.com/ | url | |
Hacker News | https://news.ycombinator.com/ | u | t = the title you want to share |
https://www.facebook.com/ | u | ||
https://www.linkedin.com/cws/share | url | ||
https://pinterest.com/ | url | media = an image to sharedescription = the text you want to share |
Using this information, I created a partial template for use on any page in this site (though I mainly use it on blog posts right now). Each link includes useful text content (e.g., “Share on ______”) and a local SVG of the service’s icon. Here’s a simplified overview of the markup I use:
<ul class="social-links social-links--share">
<li class="social-links__item">
<a href="{{ SHARE URL }}" class="social-link" rel="nofollow">
<svg>{{ SERVICE ICON }}</svg>
<b class="social-link__text">Share on {{ SERVICE NAME }}</b>
</a>
</li>
</ul>
You can check out the baseline experience on this very page by disabling JavaScript.
It’s worth noting that I have chosen not to enforce opening these links in a new tab. You can do that if you like, but on mobile devices I’d prefer the user just navigate to the share page directly. You may have a different preference, but if you decide to spawn a new tab, be sure your link text lets folks know that’s what will happen. I do include a rel="nofollow"
on the link, however, to prevent search spiders from indexing the share forms.
If you test out these links, you’ll notice many of the target forms will pick up a ton of information from your page automatically. By and large, this info is grabbed from your page’s Open Graph data (stored in meta
tags) or Linked Data (as JSON-LD). You can write that info to your page by hand or use a plugin to generate it for you automatically. There are a ton of options out there if you choose to go the later route (which I’d recommend).
Enhancement Level 1: Popup Share
If you played around with any of the various share forms, you probably noticed that they are, by and large, designed as discrete interactions best-suited to a narrow window (e.g., mobile) or popup. To provide that experience, I’ve long-relied on a little bit of JavaScript to launch them in a new, appropriately-sized window:
function popup( e ) {
var $link = e.target;
while ( $link.nodeName.toLowerCase() != "a" ) {
$link = $link.parentNode;
}
e.preventDefault();
var popup = window.open( $link.href, 'share',
'height=500,width=600,status=no,toolbar=no,popup'
);
try {
popup.focus();
e.preventDefault();
} catch(e) {}
}
var screen_width = "visualViewport" in window ?
window.visualViewport.width : window.innerWidth,
$links = document.querySelectorAll(
'.social-links--share a'
),
count = $links.length;
if ( screen_width > 600 ) {
while ( count-- ) {
$links[count].addEventListener('click', popup, false);
$links[count].querySelector(
'.social-link__text'
).innerHTML += " (in a popup)";
}
}
The first chunk defines a new function called popup()
that will act as the event listener. It takes the event (e) as an argument and then finds the associated link (bubbling up through the DOM as necessary in that while
loop). Once it finds the link, the function opens a new popup window (using window.open()
). Then, to check if the popup was blocked, it attempts (within the try…catch
) to focus it. If the focus succeeds, which means the popup wasn’t blocked, the script prevents the link from navigating the user to the href
(which is the default behavior, hence e.preventDefault()
).
The second block defines a couple of variables we’ll need. First, it captures the current screen_width using either the window’s visualViewport
(if available) or its innerWidth
(which is more old school). Next it grabs the social links ($links) and counts them for looping purposes (count).
The final block is a conditional that checks to see if the screen_width is wider than 600px (an arbitrary width that just feels right… your mileage may vary). If the screen is wider than that threshold, it loops through the links,2 adds the click handler, and adds some text to the link label to let folks know it will open a popup.
And with that, the first layer of enhancement is complete: Users with JavaScript support who also happen to be using a wider browser window will get the popup share form if the popup is allowed. If the popup isn’t allowed, they’ll default to the baseline experience.
Enhancement Level 2: OS Share
A few years back, browsers began participating in OS-level share activities. On one side, this allowed websites to share some data—URLs, text, files—to other apps on the device via navigator.share()
. On the other side of the equation, Progressive Web Apps could advertise themselves—via the Manifest’s share_target
member—as being able to receive content shared in this way.
Sharing a URL and text is really well supported. That said, it’s only been around a few years at this point and some browsers require an additional permission to use the API.3 For these reasons, it’s best to use the API as a progressive enhancement. Thankfully, it’s easy to test for support:
if ( "share" in navigator ) {
// all good!
}
For my particular implementation, I’ve decided to swap out the individual links for a single button that, when clicked, will proffer the page’s details over to the OS’s share widget. Here’s the code I use to do that:
var $links = document.querySelector('.social-links--share'),
$parent = $links.parentNode,
$button = document.createElement('button'),
title = document.querySelector('h1.p-name,title').innerText,
$description = document.querySelector(
'meta[name="og:description"],meta[name="description"]'
),
text = $description ? $description.getAttribute('content')
: '',
url = window.location.href;
$button.innerHTML = 'Share <svg>…</svg>';
$button.addEventListener('click', function(e){
navigator.share({ title, text, url });
});
$parent.insertBefore($button, $links);
$links.remove();
The first block sets up my variables:
- $links - A reference to the list (
ul
) of sharing links; - $parent - the parent container of that list;
- $button - the button I’m going to swap in for the links;
- title - The page title (either from the page’s
h1
ortitle
element); - $description - A reference to a
meta
description element; - text - The text content of that description, if one is found; and
- url - The URL to be shared.
The second block sets up the button by inserting the text “Share” and an SVG share icon and setting an event listener on it that will pass the collected info to navigator.share()
.
The third and final block swaps out the link list for the button.
Putting It All Together
The final step to putting this all together involves setting up the conditional that determines which enhancement is offered. To keep everything a bit cleaner, I’m also moving each of the enhancements into its own function:
!(function(window, document, navigator){
function prepForPopup() {
// popup code
}
function popup() {
// popup event handler
}
function swapForShareAPI() {
// share button code
}
if ("share" in navigator ) {
swapForShareAPI();
} else {
prepForPopup();
}
})(this, this.document, this.navigator);
With this setup in place, I can provide the optimal experience in browsers that support the web share API and a pretty decent fallback experience to browsers that don’t. And if none of these enhancements can be applied, users can still share my content to the places I’ve identified… no cookies or third-party widgets required.
You can see (and play with) an isolated demo of this interface over on Codepen.
Footnotes
Interesting side-note: If you own a form like this on your site, it makes a great share target. ↩︎
Why a reverse
while
loop? Well, the order of execution doesn’t matter and decrementingwhile
loops are faster in some instances. It’s a micro-optimization that boosts perf on older browsers and lower-end chipsets. ↩︎Like many modern APIs, it also requires a secure connection (HTTPS). ↩︎
Webmentions
@Aaron Hey, I wanna let you know something funky is going on with your site in Firefox on Android. Every time I try to scroll down it jumps right to the bottom of the page. I had to switch to reader mode to see the article.
@Aaron Yep, same issue here
@p4 @Aaron Came here to report same. Firefox on Android, cannot scroll down at all without being sent directly to very bottom.
@aardrian @p4 So strange. Will look into it, thanks! I think I got the same report one time but couldn't replicate.
@Aaron Grateful for the knowledge, thanks!
It's interesting to see these still get attention because I have never really trusted share buttons, and privacy extensions tend to block many of them.
To me it always felt patronizing for a site to assume that I can't trivially copy and paste the URL.
I'm also surprised at how many people haven't already been disabling third party cookies (for me it's been several years!).
Likes
Shares