However, note that high click-through rates aren’t always a positive sign. If your ad isn’t targeting the right keywords, or your ad copy, landing page, or offering isn’t helpful or relevant to a visitor, you may end up spending a lot of money on ads that don’t impact your bottom line. Therefore, spend time conducting keyword research to make sure every paid ad is relevant to your ideal customer or target audience.
How Do You Calculate Click-Through Rates?
To calculate the click-through rate on a paid ad, divide the total number of clicks on the ad by the total number of impressions (i.e. the total number of people who saw the ad).
Pro Tip: Don’t forget to multiply your result by 100 to save some extra time calculating the percentage.
Although a lot of marketers may talk about click-through rates in paid advertising, there are several ways to measure click-through rates on other channels.
Say, I want to know how many people visit my website after reading one of my blog posts. In this case, I’ll look at the click-through rate, which will tell me how many people clicked the link to my website from my blog post, out of the total number of visitors to the blog post.
People Who Click Website from Blog Post/Total Number of Blog Post Visitors x 100 = Conversion Rate
What Is Conversion Rate?
Conversion rate is a metric, shown as a percentage, that displays how many website or app visitors complete an action out of the total number of visitors.
Why Are Conversion Rates Important?
For many marketers and entrepreneurs, conversion rates are the most important metric to monitor frequently, because it directly impacts their business’ overall sales and revenue.
Chris Keller from Bizible reinforces this idea and goes so far as to outline 3 reasons why website conversions are more important than web traffic. For one, Keller argues that more traffic doesn’t necessarily mean you’ll see a spike in sales. This can be the result of several issues: attracting the wrong kind of traffic by targeting wrong keywords, your website content isn’t related to the product or service you’re selling, or your lead capture forms are broken, just to name a few.
Secondly, Keller argues that it is significantly easier and takes less time to increase web conversions than it is to increase web traffic.
Finally, Keller’s last point is that prioritizing optimizing your conversion rate, and then optimizing web traffic—instead of the other way around—would have a greater impact on your business’ ROI and profitability.
To calculate the Conversion Rate, you’ll divide the total number of visitors to your website or landing page by the number of completed goals.
Pro Tip: You can multiply this result by 100 to save some extra time calculating the percentage.
It is important to note, however, that this formula will change slightly depending on the type of conversions you’re measuring.
For example, if you’re measuring the Conversion Rate of people visiting your website who turn into leads, the formula will be:
Total Number of Leads Collected/Total Traffic to Site x 100 = Conversion Rate
Similarly, if I want to calculate how many website visitors convert into paying customers, the conversion rate formula will look like this:
Number of Sales / Total Traffic to Site x 100 = Conversion Rate
Finally, if I want to measure how many people subscribed to my newsletter after clicking my ad, the conversion rate formula will change to this:
Number of People Who Subscribe To My Newsletter/Total Number of People Who Clicked My Ad x 100 = Conversion Rate
Analytics Tools for Tracking Conversion Rates and Click-Through Rates
While there are several analytics tracking tools that can provide data about Conversion Rates and Click-Through Rates, there’s one tool that reigns supreme: Google Analytics.
Google Analytics is an incredibly versatile tool that allows you to understand who your audiences are, how they behave on your website, where they find out about your business (that is, traffic sources), and how they interact with your website content.
More specifically, Google Analytics allows you to set up Goals, which gives you the ability to track whenever a defined action is taken on your website (that is, the conversion rate), like submit a contact form or make a purchase.
Look at this short video from Google on how to use Goals within Google Analytics to track your conversion rates.
Conversion Rate vs. Click-Through Rate: Which One Should You Measure?
Digital marketers use both the conversion rate and the click-through rate to measure the success of their marketing efforts. However, as Andrew Chu from MGX Copy notes, click-through rates and conversion rate affect two different stages of the marketing/sales funnel.
At the top of the sales funnel, the click-through rate measures how many people perform an action (such as click your ad) before they get to your website.
At the middle and bottom of the sales funnel, conversion rates measure actions that people take when they’re already on your website, like submit a form, sign up for a newsletter, download an infographic, make a purchase, and others.
As an example, let’s say I want to know how many people visited my website after seeing my Facebook ad. In this case, I would want to determine the click-through rate.
If my Facebook ad earned 100,000 impressions, and 3,500 of those people clicked my ad to visit my website, that makes my click-through rate 3.5%.
Not too bad!
Now, say I want to know that how many people became email newsletter subscribers from the ones who clicked my Facebook ad. In this case, I want to measure the conversion rate.
In this case, of the 3,500 people who clicked my Facebook ad and visited my website, 40 people subscribed to my email newsletter, making the conversion rate about 1.14%.
So, which metric are you supposed to measure—conversion rate or click-through rate?
The answer depends on which stage in the marketing/sales funnel you want to optimize.
If you want to improve your website rankings or increase traffic to the blog, then you’ll probably want to focus on measuring and optimizing your click-through rate.
If you want to focus on growing your email newsletter subscription list, increasing the number of people who sign up for a free trial, or increasing the number of products you sell online, then focus on measuring and optimizing your website conversion rates.
Actionable Growth Tactics for Improving Click-Through Rates
Here are a few quick tips that can help you improve your click-through rates:
First and foremost, conduct some consumer research on your target audience. This will give you a better understanding of what type of messaging your target audience is more likely to respond to.
Use Google Keyword Planner or another keyword research tool to find specific keywords your target audience is searching for. Include negative keywords and branded keywords as well in your research.
Write ad copy that is enticing and helps your brand stand out. Use power words that convey urgency, authority, performance, advanced technology, scarcity, or social proof.
Use high-quality, eye-catching photos in your ads; but make sure your images do not contain more than 20% of overlay text.
Make sure the copy, content, and design of your landing pages are aligned with your paid ads.
Have a clear and concise call-to-action that makes it clear to the viewer what they can expect after clicking your ad.
For more in-depth information about these tips, look at these helpful resources:
Actionable Growth Tactics for Improving Conversion Rates
Conversion rate optimization is all about identifying, analyzing, testing, and improving various touchpoints at the middle and bottom levels of the marketing/sales funnel. Here are a few quick tips on how to optimize your conversion rates.
Personalize your messaging and user experiences, based on visitor behavior, preferences, or interests.
Don’t give up on website visitors who don’t convert right away. Keep them engaged with retargeting ads, where you can display services, products, or offers based on they’ve shown some interest in.
Offer customer support throughout the buying cycle. You can offer real-time chat with a customer support representative, provide Help Center informational tutorials and troubleshooting information, or answer questions visitors may have on a dedicated FAQ page.
Convert website visitors into potential leads by offering free materials in exchange for their contact information. For example, you can offer a technical white paper, an instructional e-Book, data-rich infographics, or exclusive video content.
Give website visitors several opportunities to convert. There’s a slim chance someone will visit your website for the first time and immediately decide to make a purchase. Instead, give hesitant visitors additional opportunities to convert (also known as “micro-conversions”), like giving them a chance to sign up for your email list through a smart bar, display a limited-time offer in an exit-intent pop-up, or allowing them to subscribe to a web browser and mobile push notifications for the latest updates.
A/B test various elements of your landing pages, including hero images, call-to-actions, taglines, descriptions, button positioning, the format of contact forms, and others.
Add social proof on popular landing pages. Experiment adding customer testimonials, recognizable brands you’ve worked with, or mobile app store reviews and ratings to your website and landing pages.
Also, make sure to bookmark these materials to help bolster your conversion rate optimization strategy:
(This is a sponsored article.) Designing mobile apps means going through different stages: pre-planning, visual concepts, designing, prototyping, development. After defining your project, you need to test how it will work before you begin to develop it.
This stage is captured by prototyping. Prototyping allows designers to get a feel for the functionality and flow of an app, and to preview screens and interactions. Testing with prototypes provides valuable insights into user behavior and can be used to validate the interaction model. It is possible to represent the interactivity of an app before its development, and this gives developers a global vision of an app’s functioning, user behavior and steps to afford.
Prototyping is the simulation of the final result of an app’s development. Through this step, it’s possible to show a workflow of an app and consider problems and solutions. The two fundamental roles who will work in this phase are the user interface (UI) designer, who creates the look and feel, and the user experience (UX) designer, who creates the interaction structure between elements.
There are many ways to design and create an app’s look. As a loving user of Adobe products, I work most of the time in Illustrator and Photoshop. Illustrator helps me when I create and draw UI elements, which I can simply save and use later with Adobe XD. The process is the same as I’ve done for icons and that I showed you in my previous article “Designing for User Interfaces: Icons as Visual Elements for Screen Design.”
Photoshop comes in handy when I have to work with images in UI. But that’s not all: With the latest Adobe XD release, we can bring Photoshop design files into XD very quickly, and continue prototyping our app.
Today, I’ll offer a tutorial in which we discover how to transfer our app’s design from Photoshop to XD, continuing to work on it and having fun while prototyping. Please note that I’ve used images from Pexels.com in order to provide examples for this article.
We will cover the following steps:
Simple hand sketch,
Designing In Photoshop,
Importing PSD files to XD,
Developing a prototype,
For Adobe tools, I will use Photoshop CC, Illustrator CC and XD CC — all in the 2018 versions.
Before we start designing our app, we need a plan for how to go about it. There are some questions we have to answer:
What is the app for?
What problem does it solve?
How easy is it to use?
Let’s assume we want to create an app for recipes. We want something simple: a space for pictures with ingredients and recipes.
I sketched by hand what I have in mind :
Then, I grabbed Photoshop and created my layouts.
2. Designing In Photoshop
Before we create layouts for our app, we can take advantage of a very useful resource by Adobe: free UI design resources. Because we will be designing an iOS app, I downloaded the iOS interface for Photoshop.
Feel free to experiment with the layouts you’ve downloaded.
In Photoshop, I created a new blank document from a preset for the iPhone 6 Plus:
Below is our layout, as I designed it in Photoshop. I tried to reproduce what I drew by hand earlier.
The PSD file contains four artboards. Each has its own layers.
Note: The images used in this prototype are from Pexels.com.
Let’s see how to import this PSD file into Adobe XD.
3. Importing PSD Files To Adobe XD
Let’s run Adobe XD and click on “Open.” Select our PSD file, and click “Open.”
Ta-dah! In a few seconds, you’ll see all of your PSD elements open in XD.
More importantly, all of the elements you just imported will be organized exactly as they were in Photoshop. You can see your artboards on the left:
When you select an artboard, you will see its layers on the left — exactly the way it was in Photoshop before exporting.
Let’s do something in XD to improve our layout.
Go to Artboard 3. In this artboard, I want to add some more images. I just created three spaces in Photoshop to get an idea of what I want. Now, I can add more with some very simple steps.
First, delete the second and third image. From the first rectangle, delete the image, too, by double-clicking on it. You’ll have just one rectangle.
With this rectangle selected, go to “Repeat Grid” on the right and click it. Then, grab the handle and pull it downward. The grid will replicate your rectangle, inserting as many as you want. Create six rectangles, and adjust your artboard’s height by double-clicking on it and pulling its handle downwards.
Now, select all of the images you want to put in rectangles, and drag them all together onto the grid you just created:
Et voilà! All of your pictures are in place.
Now that all of the layouts are ready, let’s play with prototyping!
4. Developing A Prototype
Let’s begin the fun part!
We have to create interactions between our artboards and elements. Click on “Prototype” in the top left, as shown in the image below.
Click on the “Start here” button. When the little blue arrow appears, click and drag it to the second artboard. We are connecting these two artboards and creating interaction by clicking the intro button. Then, you can decide which kind of interaction to use (slide, fading, time, etc.).
See how I’ve set it in the image below:
Scrolling tip: Before viewing our prototype preview, we need to do another important thing. We have to make our artboards scrollable, giving them the same effect as when pushing a screen up and down with a finger on the phone.
Let’s go back a step and click on “Design” in the top left. Check which artboards are higher — in this case, the third and fourth. Select the third artboard from the left, and you’ll see the section “Scrolling” on the right. Set it to “Vertical.”
Then, you’ll see that your “Viewport Height” is set to a number, higher than the original artboard’s size. That’s normal, because we made it higher by adding some elements. But to make our artboard scrollable, we need to set that value to the original artboard’s size — in this case, 2208 pixels, the height of the iPhone 6 Plus, which we set in Photoshop and then imported to XD.
After setting that, you’ll see a dotted line where your artboard ends. That means it is now scrollable.
To see our first interactions in action, click on “Prototype” in the top left, and then click the little arrow in the top right. See them in action below:
Let’s finish up by connecting all of our artboards, as we’ve seen before, and check our final prototype. Don’t forget to connect them “back” to the previous artboard when clicking on the little arrow to go back:
And here is the final demo:
In this tutorial, you have learned:
that you can design your app in Photoshop,
how you can bring it into Adobe XD,
how to create a simple prototype.
Decide on one primary action per screen, and highlight its containing element through visual design (e.g. a big CTA).
White space is very important on small screens. It prevents confusion and gives the user more clickable space. And here comes that rule again: One primary action works well with more white space.
If you are not on a desktop, avoid all unnecessary elements.
Always test your prototypes with regular users. They will help you to understand whether the flow is easy to follow.
Conditioner And Progressive Enhancement Sitting In A Tree
Before we proceed, I need to get one thing across:
Conditioner is not a framework for building web apps.
Instead, it’s aimed at websites. The distinction between websites and web apps is useful for the continuation of this story. Let me explain how I view the overall difference between the two.
Examples of content-oriented websites are for instance: Wikipedia, Smashing Magazine, your local municipality website, newspapers, and webshops. Web apps are often found in the utility area, think of web-based email clients and online maps. While also presenting content, the focus of web apps is often more on interacting with content than presenting content. There’s a huge grey area between the two, but this contrast will help us decide when Conditioner might be effective and when we should steer clear.
As stated earlier, Conditioner is all about websites, and it’s specifically built to deal with that third act:
The Troublesome Third Act
A class is added to an HTML element.
The querySelectorAll method is used to get all elements assigned the class.
A for-loop traverses the NodeList returned in step 2.
Let’s quickly put this workflow in code by adding autocomplete functionality to an input field. We’ll create a file called autocomplete.js and add it to the page using a <script> tag.
// our autocomplete logic
<input type="text" class="autocomplete"/>
var inputs = document.querySelectorAll('.autocomplete');
for (var i = 0; i < inputs.length; i++)
Suppose we’re now told to add another functionality to the page, say a date picker, it’s initialization will most likely follow the same pattern. Now we’ve got two for-loops. Add another functionality, and you’ve got three, and so on and so on. Not the best.
While this works and keeps you off the street, it creates a host of problems. We’ll have to add a loop to our initialization script for each functionality we add. For each loop we add, the initialization script gets linked ever tighter to the document structure of our website. Often the initialization script will be loaded on each page. Meaning all the querySelectorAll calls for all the different functionalities will be run on each and every page whether functionality is defined on the page or not.
For me, this setup never felt quite right. It always started out “okay,” but then it would slowly grow to a long list of repetitive for-loops. Depending on the project it might contain some conditional logic here and there to determine if something loads on a certain viewport or not.
if (window.innerWidth <= 480)
// small viewport for-loops here
Eventually, my initialization script would always grow out of control and turn into a giant pile of spaghetti code that I would not wish on anyone.
Something needed to be done.
That stack of spaghetti loops though, I wanted to get rid them so badly.
We’ll quickly update our script to use data attributes instead of classes.
<input type="text" data-module="autocomplete">
var inputs = document.querySelectorAll('[data-module=autocomplete]');
for (var i = 0; i < inputs.length; i++)
But hang on, this is nearly the same setup; we’ve only replaced .autocomplete with [data-module=autocomplete]. How’s that any better? It’s not, you’re right. If we add an additional functionality to the page, we still have to duplicate our for-loop — blast! Don’t be sad though as this is the stepping stone to our killer for-loop.
Watch what happens when we make a couple of adjustments.
<input type="text" data-module="createAutocomplete">
var elements = document.querySelectorAll('[data-module]');
for (var i = 0; i < elements.length; i++)
var name = elements[i].getAttribute('data-module');
var factory = window[name];
Now we can load any functionality with a single for-loop.
Find all elements on the page with a data-module attribute;
Loop over the node list;
Get the name of the module from the data-module attribute;
This basic setup has some other advantages as well:
The init script no longer needs to know what it loads; it just needs to be very good at this one little trick.
The init script does not search for modules that are not there, i.e. no wasted DOM searches.
The init script is done. No more adjustments are needed. When we add functionality to the page, it will automatically be found and will simply work.
So What About This Thing Called Conditioner?
We finally have our single loop, our one loop to rule all other loops, our king of loops, our hyper-loop. Ehm. Okay. We’ll just have to conclude that our is a loop of high quality and is so flexible that it can be re-used in each project (there’s not really anything project specific about it). That does not immediately make it library-worthy, it’s still quite a basic loop. However, we’ll find that our loop will require some additional trickery to really cover all our use-cases.
With the one loop, we are now loading our functionality automatically.
We assign a data-module attribute to an element.
We add a <script> tag to the page referencing our functionality.
The loop matches the right functionality to each element.
Let’s take a look at what we need to add to our loop to make it a bit more flexible and re-usable. Because as it is now, while amazing, we’re going to run into trouble.
It would be handy if we moved the global functions to isolated modules. This prevents pollution of the global scope. Makes our modules more portable to other projects. And we’ll no longer have to add our <script> tags manually. Fewer things to add to the page, fewer things to maintain.
When using our portable modules across multiple projects (and/or pages) we’ll probably encounter a situation where we need to pass configuration options to a module. Think API keys, labels, animation speeds. That’s a bit difficult at the moment as we can’t access the for-loop.
With the ever-growing diversity of devices out there we will eventually encounter a situation where we only want to load a module in a certain context. For instance, a menu that needs to be collapsed on small viewports. We don’t want to add if-statements to our loop. It’s beautiful as it is, we will not add if statements to our for-loop. Never.
That’s where Conditioner can help out. It encompasses all above functionality. On top of that, it exposes a plugin API so we can configure and expand Conditioner to exactly fit our project setup.
Let’s make that 1 Kilobyte jump and replace our initialization loop with Conditioner.
Switching To Conditioner
We can get the Conditioner library from the GitHub repository, npm or from unpkg. For the rest of the article, we’ll assume the Conditioner script file has been added to the page.
Conditioner will now automatically lazy load ./autocomplete.js, and once received, it will call the module.default function and pass the element as a parameter.
Defining our autocomplete as ./autocomplete.js is very verbose. It’s difficult to read, and when adding multiple modules on the page, it quickly becomes tedious to write and error prone.
This can be remedied by overriding the moduleSetName action. Conditioner views the data-module value as an alias and will only use the value returned by moduleSetName as the actual module name. Let’s automatically add the js extension and relative path prefix to make our lives a bit easier.
<input type="text" data-module="autocomplete"/>
// converts module aliases to paths
moduleSetName: (name) => `./$ name .js`
Now we can set data-module to autocomplete instead of ./autocomplete.js, much better.
That’s it! We’re done! We’ve setup Conditioner to load ES Modules. Adding modules to a page is now as easy as creating a module file and adding a data-module attribute.
The plugin architecture makes Conditioner super flexible. Because of this flexibility, it can be modified for use with a wide range of module loaders and bundlers. There’s bootstrap projects available for Webpack, Browserify and RequireJS.
Please note that Conditioner does not handle module bundling. You’ll have to configure your bundler to find the right balance between serving a bundled file containing all modules or a separate file for each module. I usually cherry pick tiny modules and core UI modules (like navigation) and serve them in a bundled file while conditionally loading all scripts further down the page.
Alright, module loading — check! It’s now time to figure out how to pass configuration options to our modules. We can’t access our loop; also we don’t really want to, so we need to figure out how to pass parameters to the constructor functions of our modules.
Passing Configuration Options To Our Modules
I might have bent the truth a little bit. Conditioner has no out-of-the-box solution for passing options to modules. There I said it. To keep Conditioner as tiny as possible I decided to strip it and make it available through the plugin API. We’ll explore some other options of passing variables to modules and then use the plugin API to set up an automatic solution.
The easiest and at the same time most banal way to create options that our modules can access is to define options on the global window scope.
We’ve only eliminated the dataset call, i.e. seven characters. Not the biggest improvement, but we’ve opened the door to take this a bit further.
Suppose we have multiple autocomplete modules on the page, and each and every single one of them requires the same API key. It would be handy if that API key was supplied automagically instead of having to add it as a data attribute on each element.
We can improve our developer lives by adding a page level configuration object.
const pageOptions =
// the module alias
key: 'abc123' // api key
// the name of the module and the element it's being mounted to
moduleSetConstructorArguments: (name, element) => ([
// merge the default page options with the options set on the element it self
As our pageOptions variable has been defined with const it’ll be block-scoped, which means it won’t pollute the global scope. Nice.
Using Object.assign we merge an empty object with both the pageOptions for this module and the dataset DOMStringMap found on the element. This will result in an options object containing both the source property and the key property. Should one of the autocomplete elements on the page have a data-key attribute, it will override the pageOptions default key for that element.
That’s some top-notch developer convenience right there.
By having added this tiny plugin, we can automatically pass options to our modules. This makes our modules more flexible and therefore re-usable over multiple projects. We can still choose to opt-out and use dataset or globally scope our configuration variables (no, don’t), whatever fits best.
Our next challenge is the conditional loading of modules. It’s actually the reason why Conditioner is named Conditioner. Welcome to the inner circle!
Conditionally Loading Modules Based On User Context
Back in 2005, desktop computers were all the rage, everyone had one, and everyone browsed the web with it. Screen resolutions ranged from big to bigger. And while users could scale down their browser windows, we looked the other way and basked in the glory of our beautiful fixed-width sites.
I’ve rendered an artist impression of the 2005 viewport:
I’ve applied this knowledge to our artist impression below.
Holy smokes! That’s a lot of viewports.
Today, someone might visit your site on a small mobile device connected to a crazy fast WiFi hotspot, while another user might access your site using a desktop computer on a slow tethered connection. Yes, I switched up the connection speeds — reality is unpredictable.
And to think we were worried about users resizing their browser window. Hah!
Note that those million viewports are not set in stone. A user might load a website in portrait orientation and then rotate the device, (or, resize the browser window), all without reloading the page. Our websites should be able to handle this and load or unload functionality accordingly.
With Conditioner in place, let’s configure it as a gatekeeper and have it load modules based on the current user context. The user context contains information about the environment in which the user is interacting with your functionality. Some examples of environment variables influencing context are viewport size, time of day, location, and battery level. The user can also supply you with context hints, for instance, a preference for reduced motion. How a user behaves on your platform will also tell you something about the context she might be in, is this a recurring visit, how long is the current user session?
The better we’re able to measure these environment variables the better we can enhance our interface to be appropriate for the context the user is in.
We’ll need an attribute to describe our modules context requirements so Conditioner can determine the right moment for the module to load and to unload. We’ll call this attribute data-context. It’s pretty straightforward.
Let’s leave our lovely autocomplete module behind and shift focus to a new module. Our new section-toggle module will be used to hide the main navigation behind a toggle button on small viewports.
Since it should be possible for our section-toggle to be unloaded, the default function returns another function. Conditioner will call this function when it unloads the module.
We don’t need the toggle behavior on big viewports as those have plenty of space for our menu (it’s a tiny menu). We only want to collapse our menu on viewports more narrow than 30em (this translates to 480px).
The data-context attribute will trigger Conditioner to automatically load a context monitor observing the media query (max-width:30em). When the user context matches this media query, it will load the module; when it does not, or no longer does, it will unload the module.
Monitoring happens based on events. This means that after the page has loaded, should the user resize the viewport or rotate the device, the user context is re-evaluated and the module is loaded or unloaded based on the new observations.
You can view monitoring as feature detection. Where feature detection is about an on/off situation, the browser either supports WebGL, or it doesn’t. Context monitoring is a continuous process, the initial state is observed at page load, but monitoring continues after. While the user is navigating the page, the context is monitored, and observations can influence page state in real-time.
The media query monitor is the only monitor that is available by default. Adding your own custom monitors is possible using the plugin API. Let’s add a visible monitor which we’ll use to determine if an element is visible to the user (scrolled into view). To do this, we’ll use the brand new IntersectionObserver API.
// the monitor hook expects a configuration object
// the name of our monitor with the '@'
// the create method will return our monitor API
create: (context, element) => (
// current match state
// called by conditioner to start listening for changes
new IntersectionObserver(entries =>
// update the matches state
this.matches = entries.pop().isIntersecting == context;
// inform Conditioner of the state change
We now have a visible monitor at our disposal.
Let’s use this monitor to only load images when they are scrolled in to view.
A red cat eating a yellow bird
The lazyImage module will extract the link text, create an image element, and set the link text to the alt text of the image.
export default (element) =>
// store original link text
const text = element.textContent;
// replace element text with image
const image = new Image();
image.src = element.href;
return () =>
// restore original element state
element.innerHTML = text
When the anchor is scrolled into view, the link text is replaced with an img tag.
Because we’ve returned an unload function the image will be removed when the element scrolls out of view. This is most likely not what we desire.
We can remedy this behavior by adding the was operator. It will tell Conditioner to retain the first matched state.
A red cat eating a yellow bird
There are three other operators at our disposal.
The not operator lets us invert a monitor result. Instead of writing @visible false we can write not @visible which makes for a more natural and relaxed reading experience.
Last but not least, we can use the or and and operators to string monitors together and form complex context requirements. Using and combined with or we can do lazy image loading on small viewports and load all images at once on big viewports.
data-context="was @visible and @media (max-width:30em) or @media (min-width:30em)">
A red cat eating a yellow bird
We’ve looked at the @media monitor and have added our custom @visible monitor. There are lots of other contexts to measure and custom monitors to build:
Tap into the Geolocation API and monitor the location of the user @location (near: 51.4, 5.4) to maybe load different scripts when a user is near a certain location.
Imagine a @time monitor, which would make it possible to enhance a page dynamically based on the time of day @time (after 20:00).
By moving context monitoring outside of our modules, our modules have become even more portable. If we need to add collapsible sections to one of our pages, it’s now easy to re-use our section toggle module, because it’s not aware of the context in which it’s used. It just wants to be in charge of toggling something.
And this is what Conditioner makes possible, it extracts all distractions from the module and allows you to write a module focused on a single task.
Conditioner exposes a total of three methods. We’ve already encountered the hydrate and addPlugin methods. Let’s now have a look at the monitor method.
The monitor method lets us manually monitor a context and receive context updates.
const monitor = conditioner.monitor('@media (min-width:30em)');
monitor.onchange = (matches) =>
// called when a change to the context was observed
As a quick example, I’ve built a React <ContextRouter> component that uses Conditioner to monitor user context queries and switch between views. It’s heavily inspired by React Router so might look familiar.
<Context query="@media (min-width:30em)"
component= FancyInfoGraphic />
// fallback to use on smaller viewports
I hope someone out there is itching to convert this to Angular. As a cat and React person I just can’t get myself to do it.
Replacing our initialization script with the killer for loop created a single entity in charge of loading modules. From that change, automatically followed a set of requirements. We used Conditioner to fulfill these requirements and then wrote custom plugins to extend Conditioner where it didn’t fit our needs.
Not having access to our single for loop, steered us towards writing more re-usable and flexible modules. By switching to dynamic imports we could then lazy load these modules, and later load them conditionally by combining the lazy loading with context monitoring.
With conditional loading, we can quickly determine when to send which module over the connection, and by building advanced context monitors and queries, we can target more specific contexts for enhancement.
By combining all these tiny changes, we can speed up page load time and more closely match our functionality to each different context. This will result in improved user experience and as a bonus improve our developer experience as well.
Software developers all over the world can benefit from an increased understanding of intellectual property (IP) laws and how those laws may affect their work. Software programs are often complex works that include both functional and artistic elements and may be covered by a variety of different types of IP laws. This can be very confusing for those who haven’t been taught about IP and can cause them to miss out on opportunities to protect their own work or to accidentally infringe on the work of another.
The purpose of this article is to provide information about one type of IP law, copyright law, for software developers who live or work in the United Kingdom. Below we will discuss the definition of copyright law, the source of UK copyright law, and how it applies to technological works. I’ll also elaborate on what is not covered by copyright law, as well as the UK concepts of fair dealing and moral rights as they are related to copyright law.
Copyright Law Essentials
You can learn more about copyright law in general and about how it applies to software in my previous article. Go to article →
What Is Copyright Law?
Copyright law is a type of intellectual property law that protects creative works, which can include things like plays, movies, drawings, songs, and many other things. Around the world, copyright laws give the authors or creators of literary, dramatic, musical, or artistic works the right to control the ways in which their material may be used. With regard to software, copyright law generally covers the artistic elements of a software program as opposed to the functional elements.
What Is The Source Of Copyright Law In The UK?
Copyright law originated in the United Kingdom from a concept of common law; the Statute of Anne 1709. It became statutory with the passing of the Copyright Act 1911. The current act is the Copyright, Designs and Patents Act of 1988. Those interested can read the full text here.
The relevant government office for copyright inquiries is the UK Intellectual Property Office. The UK is also a signatory to the Berne Convention, an international agreement concerning copyright law that has been adopted by 172 countries worldwide.
How Does UK Copyright Law Apply Specifically To Technological Works?
Copyright law can apply to all kinds of technological works that are used with computers, tablets, smartphones, or video game systems. This includes apps, computer programs, databases, spreadsheets, screen displays, and even virtual reality environments. Copyright also applies to works that are used or distributed on the internet like websites, blogs, and other online content. In the UK, computer programs are specifically protected as literary works.
Throughout the European Union, the Computer Programs Directive provides guidance regarding the legal protection of computer programs. The Copyright (Computer Programs) Regulations of 1992 extended the rules covering literary works to include computer programs in other European countries as well.
What Is Not Covered By UK Copyright Law?
Copyright law in the UK, as elsewhere, does not protect ideas, procedures, methods of operations, or mathematical concepts (though other types of IP may protect them under certain circumstances). In other words, copyright law is about protecting a particular expression of an idea, not the idea itself, and not functional elements of a work. Additionally, names, titles, short phrases, and colors are not generally considered unique or substantial enough to be covered by copyright law. However, a work that combines some of the elements, such as a logo or design, could possibly be eligible for copyright (and perhaps trademark) protection.
How Long Does Copyright Protection In The UK Last?
Because the UK is a signatory to the Berne Convention which covered this issue, a copyright in the UK will typically be protected for either the life of the author plus 70 years from the death of the author or, for published works, for 70 years from the date of first publication. However, there are many exceptions to this rule, and each work should be treated on a case-by-case basis if there are any doubts.
One notable UK-specific exception has to do with the boy who never grew up, Peter Pan. Author J.M. Barrie gifted all of the rights to his creation to a children’s hospital in London. When the original copyright expired in 1987, an extension was added to the Copyright, Designs and Patents Act of 1988 mentioned above so that the hospital could continue to collect royalties based on uses of the work (though the hospital has no creative control over how the work is used). Ultimately, this is only an unusual — and perhaps endearingly British — exception to the normal copyright term.
What Is Fair Dealing?
The copyright laws of almost all countries allow exceptions for certain permitted uses of copyrighted works such as news reporting, educational uses, or where the use of the work is de minimus. In the United States, one can assert a “fair use” defense if accused of infringing a copyright if the use was due to one of these permitted activities. In the UK, these permitted activities fall under the legal concept known as “fair dealing.” According to the University of Nottingham, eligible activities which can be conducted without infringing a copyrighted work include:
Private and research study purposes;
Performance, copies or lending for educational purposes;
Criticism and news reporting;
Copies and lending by librarians;
Format shifting or back up of a work you own for personal use;
Caricature, parody or pastiche;
Acts for the purposes of royal commissions, statutory enquiries, judicial proceedings and parliamentary purposes;
Recording of broadcasts for the purposes of listening to or viewing at a more convenient time;
Producing a back-up copy for personal use of a computer program.
How Does “Fair Dealing” Affect Technology Copyrights In The UK?
The “fair dealing” exceptions mentioned above may specifically impact copyrights for technology-related works such as software programs or databases. For example, producing a backup copy of a software program for personal use only would not be considered copyright infringement under a fair dealing exception. Though fair dealing explicitly excludes decompilation or copying a software program during decompilation, the European Software Directive allows software licensees to use their copy of the software “to observe study or test the functioning of the program” in order to “determine the ideas and principles which underlie any element of the program.”
Therefore, users may freely observe a program as it operates to determine their functions and its underlying ideas, even if the goal is to create a competing program (see the UK case SAS Institute v. World Programming for more information on this concept). However, actual copying, for example in the case of source code copying, is not tolerated since this is explicitly protected by copyright.
For practical reasons, database copyrights would not be infringed if a person with the legal right to use part or all of a database performs steps necessary to use or access the contents of the database. Also, accessing a database for the purposes of private study or non-commercial research does not infringe copyright in a database.
Moral Rights In The UK
Another difference between the UK and other parts of the world with regard to copyright law is the UK’s emphasis on the importance of moral rights. Though this issue may not often arise in technology-related copyright disputes, moral rights are additional rights over and above the economic rights typically protected by copyright law.
In the UK, moral rights are: the right to attribution, or the right to be known or recognized as the author of a work; the right to object to derogatory treatment of a work, which includes any addition, deletion, or adaptation of a work that would distort or “mutilate” the work or injure the honor or reputation of the author; the right to object to false attribution, which basically means that you would not be named as the author of something you didn’t create; and the right to privacy of certain photographs and recordings, such as those commissioned for a private occasion.
One reason moral rights might be important for developers is that the moral right to attribution gives the developer the right to be named as the author of the software program, even though it is not common industry practice to do so. By the same token, if a developer doesn’t get their name associated with projects they didn’t work on, the right to object to false attribution protects them also. Find more information about moral rights here.
It is our hope that this information has been helpful for UK software designers and developers. Though this is only introductory information, and should not be substituted for legal counsel in the event of specific questions or disputes, education about copyright law issues and other IP issues helps to empower software designers and developers to make sure their works are fully protected.
As the web continues to evolve and new browser technologies continue to emerge, the line between native and web development becomes more and more blurred. New APIs are unlocking the ability to code entirely new categories of software in the browser.
Until recently, the ability to interact with digital musical instruments has been limited to native, desktop applications. The Web MIDI API is here to change that.
What Is MIDI?
MIDI has been around for a long time but has only recently made its debut in the browser. MIDI (Musical Instrument Digital Interface) is a technical standard that was first published in 1983 and created the means for digital instruments, synthesizers, computers, and various audio devices to communicate with each other. MIDI messages relay musical and time-based information back and forth between devices.
A typical MIDI setup might consist of a digital piano keyboard which can send messages relaying information such as pitch, velocity (how loudly or softly a note is played), vibrato, volume, panning, modulation, and more to a sound synthesizer which converts that into audible sound. The keyboard could also send its signals to desktop music scoring software or a digital audio workstation (DAW) which could then convert the signals into written notation, save them as a sound file, and more.
MIDI is a fairly versatile protocol, too. In addition to playing and recording music, it has become a standard protocol in stage and theater applications, as well, where it is often used to relay cue information or control lighting equipment.
MIDI In The Browser
First, there’s the navigator.requestMIDIAccess() method. It does exactly what it sounds like—it will request access to any MIDI devices (inputs or outputs) connected to your computer. You can confirm the browser supports the API by checking for the existence of this method.
console.log('This browser supports WebMIDI!');
console.log('WebMIDI is not supported in this browser.');
Second, there’s the MIDIAccess object which contains references to all available inputs (such as piano keyboards) and outputs (such as synthesizers). The requestMIDIAccess() method returns a promise, so we need to establish success and failure callbacks. And if the browser is successful in connecting to your MIDI devices, it will return a MIDIAccess object as an argument to the success callback.
var inputs = midi.inputs;
var outputs = midi.outputs;
console.log('Could not access your MIDI devices.');
Third, MIDI messages are conveyed back and forth between inputs and outputs with a MIDIMessageEvent object. These messages contain information about the MIDI event such as pitch, velocity (how softly or loudly a note is played), timing, and more. We can start collecting these messages by adding simple callback functions (listeners) to our inputs and outputs.
Let’s dig in. To send MIDI messages from our MIDI devices to the browser, we’ll start by adding an onmidimessage listener to each input. This callback will be triggered whenever a message is sent by the input device, such as the press of a key on the piano.
We can loop through our inputs and assign the listener like this:
for (var input of midiAccess.inputs.values())
input.onmidimessage = getMIDIMessage;
The MIDIMessageEvent object we get back contains a lot of information, but what we’re most interested in is the data array. This array typically contains three values (e.g. [144, 72, 64]). The first value tells us what type of command was sent, the second is the note value, and the third is velocity. The command type could be either “note on,” “note off,” controller (such as pitch bend or piano pedal), or some other kind of system exclusive (“sysex”) event unique to that device/manufacturer.
For the purposes of this article, we’ll just focus on properly identifying “note on” and “note off” messages. Here are the basics:
A command value of 144 signifies a “note on” event, and 128 typically signifies a “note off” event.
Note values are on a range from 0–127, lowest to highest. For example, the lowest note on an 88-key piano has a value of 21, and the highest note is 108. A “middle C” is 60.
Velocity values are also given on a range from 0–127 (softest to loudest). The softest possible “note on” velocity is 1.
A velocity of 0 is sometimes used in conjunction with a command value of 144 (which typically represents “note on”) to indicate a “note off” message, so it’s helpful to check if the given velocity is 0 as an alternate way of interpreting a “note off” message.
Given this knowledge, we can expand our getMIDIMessage handler example above by intelligently parsing our MIDI messages coming from our inputs and passing them along to additional handler functions.
var command = message.data;
var note = message.data;
var velocity = (message.data.length > 2) ? message.data : 0; // a velocity value might not be included with a noteOff command
case 144: // noteOn
if (velocity > 0)
case 128: // noteOff
// we could easily expand this switch statement to cover other types of commands such as controllers or sysex
Browser Compatibility And Polyfill
As of the writing of this article, the Web MIDI API is only available natively in Chrome, Opera, and Android WebView.
For all other browsers that don’t support it natively, Chris Wilson’s WebMIDIAPIShim library is a polyfill for the Web MIDI API, of which Chris is a co-author. Simply including the shim script on your page will enable everything we’ve covered so far.
if (navigator.requestMIDIAccess) //... returns true
This shim also requires Jazz-Soft.net’s Jazz-Plugin to work, unfortunately, which means it’s an OK option for developers who want the flexibility to work in multiple browsers, but an extra barrier to mainstream adoption. Hopefully, within time, other browsers will adopt the Web MIDI API natively.
Making Our Job Easier With WebMIDI.js
We’ve only really scratched the surface of what’s possible with the WebMIDI API. Adding support for additional functionality besides basic “note on” and “note off” messages starts to get much more complex.
// Viewing available inputs and outputs
// Retrieve an input by name, id or index
var input = WebMidi.getInputByName("My Awesome Keyboard");
// input = WebMidi.getInputById("1809568182");
// input = WebMidi.inputs;
// Listen for a 'note on' message on all channels
console.log("Received 'noteon' message (" + e.note.name + e.note.octave + ").");
// Listen to pitch bend message on channel 3
console.log("Received 'pitchbend' message.", e);
// Listen to control change message on all channels
console.log("Received 'controlchange' message.", e);
// Remove all listeners for 'noteoff' on all channels
// Remove all listeners on the input
Real-World Scenario: Building A Breakout Room Controlled By A Piano Keyboard
A few months ago, my wife and I decided to build a “breakout room” experience in our house to entertain our friends and family. We wanted the game to include some kind of special effect to help elevate the experience. Unfortunately, neither of us have mad engineering skills, so building complex locks or special effects with magnets, lasers, or electrical wiring was outside the realm of our expertise. I do, however, know my way around the browser pretty well. And we have a digital piano.
Thus, an idea was born. We decided that the centerpiece of the game would be a series of passcode locks on a computer that players would have to “unlock” by playing certain note sequences on our piano, a la Willy Wonka.
Sound cool? Here’s how I did it.
We’ll begin by requesting WebMIDI access, identifying our keyboard, attaching the appropriate event listeners, and creating a few variables and functions to help us step through the various stages of the game.
// Variable which tell us what step of the game we're on.
// We'll use this later when we parse noteOn/Off messages
var currentStep = 0;
// Request MIDI access
console.log('This browser supports WebMIDI!');
console.log('WebMIDI is not supported in this browser.');
// Function to run when requestMIDIAccess is successful
var inputs = midiAccess.inputs;
var outputs = midiAccess.outputs;
// Attach MIDI event "listeners" to each input
for (var input of midiAccess.inputs.values())
input.onmidimessage = getMIDIMessage;
// Function to run when requestMIDIAccess fails
console.log('Error: Could not access MIDI devices.');
// Function to parse the MIDI messages we receive
// For this app, we're only concerned with the actual note value,
// but we can parse for other information, as well
var command = message.data;
var note = message.data;
var velocity = (message.data.length > 2) ? message.data : 0; // a velocity value might not be included with a noteOff command
case 144: // note on
if (velocity > 0)
case 128: // note off
// we could easily expand this switch statement to cover other types of commands such as controllers or sysex
// Function to handle noteOn messages (ie. key is pressed)
// Think of this like an 'onkeydown' event
// Function to handle noteOff messages (ie. key is released)
// Think of this like an 'onkeyup' event
// This function will trigger certain animations and advance gameplay
// when certain criterion are identified by the noteOn/noteOff listeners
// For instance, a lock is unlocked, the timer expires, etc.
Step 1: Press Any Key To Begin
To kick off the game, let’s have the players press any key to begin. This is an easy first step which will clue them into how the game works and also start a countdown timer.
// If the game hasn't started yet.
// The first noteOn message we get will run the first sequence
// Run our start up sequence
// Increment the currentStep so this is only triggered once
// Now we'll start a countdown timer...
// code to trigger animations, give a clue for the first lock
Step 2: Play The Correct Note Sequence
For the first lock, the players must play a particular sequence of notes in the right order. I’ve actually seen this done in a real breakout room, only it was with an acoustic upright piano rigged to a lock box. Let’s re-create the effect with MIDI.
For every “note on” message received, we’ll append the numeric note value to an array and then check to see if that array matches a predefined array of note values.
We’ll assume some clues in the breakout room have told the players which notes to play. For this example, it will be the beginning of the tune to “Amazing Grace” in the key of F major. That note sequence would look like this.
The MIDI note values in array form would be: [60, 65, 69, 65, 69, 67, 65, 62, 60].
var correctNoteSequence = [60, 65, 69, 65, 69, 67, 65, 62, 60]; // Amazing Grace in F
var activeNoteSequence = ;
// ... (case 0)
// The first lock - playing a correct sequence
// when the array is the same length as the correct sequence, compare the two
if (activeNoteSequence.length == correctNoteSequence.length)
var match = true;
for (var index = 0; index < activeNoteSequence.length; index++)
if (activeNoteSequence[index] != correctNoteSequence[index])
match = false;
// Run the next sequence and increment the current step
// Clear the array and start over
activeNoteSequence = ;
// code to trigger animations and give clue for the next lock
Step 3: Play The Correct Chord
The next lock requires the players to play a combination of notes at the same time. This is where our “note off” listener comes in. For every “note on” message received, we’ll add that note value to an array; for every “note off” message received, we’ll remove that note value from the array. Therefore, this array will reflect which notes are currently being pressed at any time. Then, it’s a matter of checking that array every time a note value is added to see if it matches a master array with the correct values.
For this clue, we’ll make the correct answer a C7 chord in root position starting on middle C. That looks like this.
The correct MIDI note values for this chord are: [60, 64, 67, 70].
var correctChord = [60, 64, 67, 70]; // C7 chord starting on middle C
var activeChord = ;
// ... (case 0, 1)
// add the note to the active chord array
// If the array is the same length as the correct chord, compare
if (activeChord.length == correctChord.length)
var match = true;
for (var index = 0; index < activeChord.length; index++)
if (correctChord.indexOf(activeChord[index]) < 0)
match = false;
// Remove the note value from the active chord array
// code to trigger animations, stop clock, end game
Now all that’s left to do is to add some additional UI elements and animations and we have ourselves a working game!
Here’s a video of the entire gameplay sequence from start to finish. This is running in Google Chrome. Also shown is a virtual MIDI keyboard to help visualize which notes are currently being played. For a normal breakout room scenario, this can run in full-screen mode and with no other inputs in the room (such as a mouse or computer keyboard) to prevent users from closing the window.
If you don’t have a physical MIDI device laying around and you still want to try it out, there are a number of virtual MIDI keyboard apps out there that will let you use your computer keyboard as a musical input, such as VMPK. Also, if you’d like to further dissect everything that’s going on here, check out the complete prototype on CodePen.
MIDI.org says that “Web MIDI has the potential to be one of the most disruptive music [technologies] in a long time, maybe as disruptive as MIDI was originally back in 1983.” That’s a tall order and some seriously high praise.
I hope this article and sample app has gotten you excited about the potential that this API has to spur the development of new and exciting kinds of browser-based music applications. In the coming years, hopefully, we’ll start to see more online music notation software, digital audio workstations, audio visualizers, instrument tutorials, and more.
If you want to read more about Web MIDI and its capabilities, I recommend the following:
Take a moment and think about a first meeting with a prospective customer. A good salesman will not try to sell right away. Instead, he will start by asking specific questions and subsequently use the answers provided to give valuable advice. Why does this work? Because in this way, trust is developed between both parties. This trust forms the necessary foundation for a sales transaction to take place further down the road. If a prospect visits your website, you’ll want to apply this principle of building trust in an online environment. Therefore, you typically provide useful content on your site such as articles, white…
Service Worker is probably one of the most misrepresented technologies we currently have. When I hear people talking about it, the topic almost always revolves around serving an app when a user is offline. However, Service Worker can do so much more than that, and every week I come across new articles that show how powerful the technology really is.
This month, for example, we can learn how to use Service Worker for cross-tab messaging and to load off requests into the background with the Background Sync API. I think the toolset we now have in our browsers already allows us to build great experiences regardless of the network state. Now it’s up to us to make the experiences so great that users truly love them. And that’s probably the hardest part.
Sketch 49 brings prototyping as native functionality.
Webpack 4 was released and brings along build performance improvements of up to 98% and easier configuration.
Ed Ellson examined Chrome’s Background Sync API and the retry strategy it uses to perform a request. By allowing synchronization in the background after a first attempt has failed, the API helps us improve the browsing experience for users who go offline or are on unstable connections.
With GraphQL you can query exactly what you want whenever you want. This is amazing for working with an API but also has complex security implications. Instead of asking for legitimate, useful data, a malicious actor could submit an expensive, nested query to overload your server, database, network, or all of these. To prevent this from happening, Max Stoiber shows us how we can secure the GraphQL API in our projects.
WebKit is introducing the Storage Access API. The new API targets one of the major issues with Safari’s Intelligent Tracking Protection (ITP): Identifying users who are logged in to a first-party service but view content of it embedded on a third party (YouTube videos on a blog, for example). The Storage Access API allows third-party embeds to request access to their first-party cookies when the user interacts with them. A good solution to protect user privacy by default and allow exceptions on request.
A year after Facebook’s announcement to broadly use Cache-Control: Immutable, Paul Calvano examined how widespread its usage is on the web — apart from the few big players. Interesting research and it’s still sad to see that this useful performance tool is used so little. At Colloq, we use it quite a lot, which saves us a lot of traffic and load on our servers and enables us to serve a lot of pages nearly instantly to recurring users.
Jad Joubran shares how to run fetch in a Web Worker to offload it from the main thread into its own. This could be a useful experiment for tasks where expensive requests are triggered, and maybe even at regular intervals.
Preethi Sam shares various tricks on how to create knockout text effects with pure CSS. Interesting to see how many different techniques we have nowadays to create such effects.
Heydon Pickering wrote about building inclusive notifications, not only from a technical perspective but the user experience point of view. This resource gives useful advice for designing notifications, what content to include, and how and when to present them to users.
Vox Media shared their accessibility guidelines. A great interactive checklist we can use to confirm that we did our job correctly.
Work & Life
This week I read an article by Alex Duloz, and his words still stick with me: “When we develop a new application, when we post content on the Internet, whatever we do that people will have access to, we should consider just for a minute if our contribution adds up to the level of dumbness kids/teenagers are exposed to. If it does, we should refrain from going live.” The truth is, most of us, including me, don’t consider this before posting on the Internet. We create funny things, share funny pictures and try to get fame with silly posts. But in reality, we shape society with this. Let’s try to provide more useful resources and make the consumption of this more enjoyable so young people can profit from our knowledge and not only view things we think are funny. “We should always consider how teenagers will use what we release.”
There’s an important article on how unhappiness has grown in America’s population since around the year 2000. It reveals that while income inequality might play a role, the more important aspect is that young people who use a lot of digital media are unhappier than those who use it only up to an hour a day. Interestingly, people who don’t use digital media at all, are unhappy, too, so the outcome of this could be that we should try to use digital media only moderately — at least in our private lives. I bet it’ll make a big difference.
How do you know your content marketing is effective? It’s not a rhetorical question, though it may seem like it. While it’s difficult to measure the success and return on investment of content purely quantitatively, you can absolutely use digital analytics to get some directional insights as well as insights that help you improve your approach. The good thing: there are no shortage of tools and guides to help you do that nowadays. The bad thing: it can be a bit overwhelming when you think about how to get started with digital analytics, especially if you’ve got a content calendar…
Each application is a unique challenge to produce in its own right, but even more so when you consider that we have to deploy most projects in manydifferentlanguages. Our content has to work not only on the BBC News and Sports websites but on their equivalent apps on iOS and Android, as well as on third-party sites which consume BBC content.
Now consider that there is an increasing array of new platforms such as AMP, Facebook Instant Articles, and Apple News. Each platform has its own limitations and proprietary publishing mechanism. Creating interactive content that works across all of these environments is a real challenge. I’m going to describe how we’ve approached the problem at the BBC.
Example: Canonical vs. AMP
This is all a bit theoretical until you see it in action, so let’s delve straight into an example.
Here is a BBC article containing Visual Journalism content:
This is the canonical version of the article, i.e., the default version, which you’ll get if you navigate to the article from the homepage.
While the canonical and AMP versions look the same, they are actually two different endpoints with different behavior:
The canonical version scrolls you to your chosen country when you submit the form.
The AMP version doesn’t scroll you, as you cannot scroll the parent page from within an AMP iframe.
The AMP version shows a cropped iframe with a ‘Show More’ button, depending on viewport size and scroll position. This is a feature of AMP.
As well as the canonical and AMP versions of this article, this project was also shipped to the News App, which is yet another platform with its own intricacies and limitations. So how do we do support all of these platforms?
Tooling Is Key
We don’t build our content from scratch. We have a Yeoman-based scaffold which uses Node to generate a boilerplate project with a single command.
Out of the box, this works pretty well for compiling for one platform but we need to support multiple platforms. Let’s delve into some code.
Embed vs. Standalone
In Visual Journalism, we sometimes output our content inside an iframe so that it can be a self-contained “embed” in an article, unaffected by the global scripting and styling. An example of this is the Donald Trump interactive embedded in the canonical example earlier in this article.
On the other hand, sometimes we output our content as raw HTML. We only do this when we have control over the whole page or if we require really responsive scroll interaction. Let’s call these our “embed” and “standalone” outputs respectively.
Imagine doing an equivalent of this for every meaningful DOM interaction in your project. Once you’ve finished shuddering, make yourself a relaxing cup of tea, and read on.
Abstraction Is Key
Rather than forcing our developers to handle these conditionals inside their code, we built an abstraction layer between their content and the environment. We call this layer the ‘wrapper.’
Instead of querying the DOM or native browser events directly, we can now proxy our request through the wrapper module.
import wrapper from 'wrapper';
button.on('click', () =>
Each platform has its own wrapper implementation conforming to a common interface of wrapper methods. The wrapper wraps itself around our content and handles the complexity for us.
The standalone wrapper’s implementation of the scrollTo function is very simple, passing our argument directly to window.scrollTo under the hood.
Now let’s look at a separate wrapper implementing the same functionality for the iframe:
The “embed” wrapper takes the same argument as in the “standalone” example but manipulates the value so that the iframe offset is taken into account. Without this addition, we would have scrolled our user somewhere completely unintended.
The Wrapper Pattern
Using wrappers results in code that is cleaner, more readable and consistent between projects. It also allows for micro-optimisations over time, as we make incremental improvements to the wrappers to make their methods more performant and accessible. Your project can, therefore, benefit from the experience of many developers.
So, what does a wrapper look like?
Each wrapper essentially comprises three things: a Handlebars template, wrapper JS file, and a SASS file denoting wrapper-specific styling. Additionally, there are build tasks which hook into events exposed by the underlying scaffolding so that each wrapper is responsible for its own pre-compilation and cleanup.
scss/wrapper.scss contains wrapper-specific styling that your application code shouldn’t need to define itself. The embed wrapper, for example, replicates a lot of BBC News styling inside the iframe.
Finally, js/wrapper.js contains the iframed implementation of the wrapper API, detailed below. It is shipped separately to the project, rather than compiled in with the application code — we flag wrapper as a global in our Webpack build process. This means that though we deliver our application to multiple platforms, we only compile the code once.
The wrapper API abstracts a number of key browser interactions. Here are the most important ones:
Scrolls to the given position in the active window. The wrapper will normalise the provided integer before triggering the scroll so that the host page is scrolled to the correct position.
Returns the user’s current (normalized) scroll position. In the case of the iframe, this means that the scroll position passed to your application is actually negative until the iframe is at the top of the viewport. This is super useful and lets us do things such as animate a component only when it comes into view.
Provides a hook into the scroll event. In the standalone wrapper, this is essentially hooking into the native scroll event. In the embed wrapper, there will be a slight delay in receiving the scroll event since it is passed via postMessage.
viewport: height: int, width: int
A method to retrieve the viewport height and width (since this is implemented very differently when queried from within an iframe).
In standalone mode, we hide the BBC menu and footer from view and set a position: fixed on our content. In the News App, we do nothing at all — the content is already full screen. The complicated one is the iframe, which relies on applying styles both inside and outside the iframe, coordinated via postMessage.
Tell the wrapper your content has loaded. This is crucial for our content to work in the News App, which will not attempt to display our content to the user until we explicitly tell the app our content is ready. It also removes the loading spinner on the web versions of our content.
List Of Wrappers
In the future, we envisage creating additional wrappers for large platforms such as Facebook Instant Articles and Apple News. We have created six wrappers to date:
The version of our content that should go in standalone pages. Comes bundled with BBC branding.
The iframed version of our content, which is safe to sit inside articles or to syndicate to non-BBC sites, since we retain control over the content.
This is the endpoint which is pulled in as an amp-iframe into AMP pages.
News App Wrapper
Our content must make calls to a proprietary bbcvisualjournalism:// protocol.
A JSON representation of our content, for sharing across BBC products.
Wiring Wrappers Up To The Platforms
For our content to appear on the BBC site, we provide journalists with a namespaced path:
/include/[department]/[unique ID], e.g. /include/visual-journalism/123-quiz
The journalist puts this “include path” into the CMS, which saves the article structure into the database. All products and services sit downstream of this publishing mechanism. Each platform is responsible for choosing the flavor of content it wants and requesting that content from a proxy server.
The AMP renderer does a little magic to render some AMP HTML which references our content, pulling in the /amp version as an iframe:
<amp-iframe src="https://news.files.bbci.co.uk/include/newsspec/15996-trump-tracker/english/index/amp" width="640" height="360">
<!-- some other AMP elements here -->
Every supported platform has its own version of the content:
...and so on
This solution can scale to incorporate more platform types as they arise.
Abstraction Is Hard
Building a “write once, deploy anywhere” architecture sounds quite idealistic, and it is. For the wrapper architecture to work, we have to be very strict on working within the abstraction. This means we have to fight the temptation to “do this hacky thing to make it work in [insert platform name here].” We want our content to be completely unaware of the environment it is shipped in — but this is easier said than done.
Features Of The Platform Are Hard To Configure Abstractly
Before our abstraction approach, we had complete control over every aspect of our output, including, for example, the markup of our iframe. If we needed to tweak anything on a per-project basis, such as add a title attribute to the iframe for accessibility reasons, we could just edit the markup.
Now that the wrapper markup exists in isolation from the project, the only way of configuring it would be to expose a hook in the scaffold itself. We can do this relatively easily for cross-platform features, but exposing hooks for specific platforms breaks the abstraction. We don’t really want to expose an ‘iframe title’ configuration option that’s only used by the one wrapper.
We could name the property more generically, e.g. title, and then use this value as the iframe title attribute. However, it starts to become difficult to keep track of what is used where, and we risk abstracting our configuration to the point of no longer understanding it. By and large, we try to keep our config as lean as possible, only setting properties that have global use.
Component Behaviour Can Be Complex
On the web, our sharetools module spits out social network share buttons that are individually clickable and open a pre-populated share message in a new window.
In the News App, we don’t want to share through the mobile web. If the user has the relevant application installed (e.g. Twitter), we want to share in the app itself. Ideally, we want to present the user with the native iOS/Android share menu, then let them choose their share option before we open the app for them with a pre-populated share message. We can trigger the native share menu from the app by making a call to the proprietary bbcvisualjournalism:// protocol.
However, this screen will be triggered whether you tap ‘Twitter’ or ‘Facebook’ in the ‘Share your results’ section, so the user ends up having to make their choice twice; the first time inside our content, and a second time on the native popup.
This is a strange user journey, so we want to remove the individual share icons from the News app and show a generic share button instead. We are able to do this by explicitly checking which wrapper is in use before we render the component.
Building the wrapper abstraction layer works well for projects as a whole, but when your choice of wrapper affects changes at the component level, it’s very difficult to retain a clean abstraction. In this case, we’ve lost a little abstraction, and we have some messy forking logic in our code. Thankfully, these cases are few and far between.
How Do We Handle Missing Features?
Keeping abstraction is all well and good. Our code tells the wrapper what it wants the platform to do, e.g. “go full screen.” But what if the platform we’re shipping to can’t actually go full-screen?
The wrapper will try its best not to break altogether, but ultimately you need a design which gracefully falls back to a working solution whether or not the method succeeds. We have to design defensively.
Let’s say we have a results section containing some bar charts. We often like to keep the bar chart values at zero until the charts are scrolled into view, at which point we trigger the bars animating to their correct width.
But if we have no mechanism to hook into the scroll position — as is the case in our AMP wrapper — then the bars would forever remain at zero, which is a thoroughly misleading experience.
We are increasingly trying to adopt more of a progressive enhancement approach in our designs. For example, we could provide a button which will be visible for all platforms by default, but which gets hidden if the wrapper supports scrolling. That way, if the scroll fails to trigger the animation, the user can still trigger the animation manually.
Plans For The Future
We hope to develop new wrappers for platforms such as Apple News and Facebook Instant Articles, as well as to offer all new platforms a ‘core’ version of our content out of the box.
We also hope to get better at progressive enhancement; succeeding in this field means developing defensively. You can never assume all platforms now and in the future will support a given interaction, but a well-designed project should be able to get its core message across without falling at the first technical hurdle.
Working within the confines of the wrapper is a bit of a paradigm shift, and feels like a bit of a halfway house in terms of the long-term solution. But until the industry matures onto a cross-platform standard, publishers will be forced to roll out their own solutions, or use tooling such as Distro for platform-to-platform conversion, or else ignore entire sections of their audience altogether.
It’s early days for us, but so far we’ve had great success in using the wrapper pattern to build our content once and deliver it to the myriad of platforms our audiences are now using.