Tag: Over

Why would a business push a native app over a website?

I wanted to write down what I think the reasons are here in December of 2021 so that we might revisit it from time to time in the future and see if these reasons are still relevant. I’m a web guy myself, so I’m interested in seeing how the web can evolve to mitigate these concerns.

I’m exclusively focusing on reasons a native app might either be a distinct advantage or at least feel like an advantage compared to a website. Nothing subjective here, like “it’s faster to develop on” or “native apps are more intuitive” or the like, which are too subjective to quantify. I’m also not getting into reasons where the web has the advantage. But in case you are unsure, there are many: it’s an open standardized platform, it will outlast closed systems, it strongly values backward compatibility, anybody can build for the web, it runs cross-platform, and heck, URLs alone are reason enough to go web-first. But that’s not to say native apps don’t have some extremely compelling things they offer, hence this post.

Because they get an icon on the home screen of the device.

It’s a mindshare thing. You pick up your phone, the icon is staring you in the face, begging to be opened. A widely cited report from a few years back suggests 90% of phone usage is in apps (as opposed to a mobile web browser), even though there is plenty of acknowledged gray area there. So, theoretically, you get to be part of that 90% if you make an app rather than being booted into the sad 10% zone.

If reach is the top concern, it seems like the best play is having both a web app and a native app. That way, you’re benefitting from the share-ability and search-ability that URLs give you, along with a strong presence on the device.

Looking at my own phone’s home screen, the vast majority of the apps are both native and web apps: Google Calendar, AccuWeather, Google Maps, Spotify, Notion, Front, Pocket Casts, Instagram, Discord, Twitter, GitHub, Slack, and Gmail. That’s a lot of big players doing it both ways.

Potential Solution: Both iOS and Android have “Add to Home Screen” options for websites. It’s just a fairly “buried” feature and it’s unlikely all that many people use it. Chrome on Android goes a step further, offering a Native App Install Prompt for apps for Progressive Web App (PWA) websites that meet a handful of extra criteria, like the site has been interacted with for at least 30 seconds. Native App Install Prompts are a big tool that levels this playing field, and it would be ideal to see Apple support PWAs better and offer this. There isn’t that much we can do as website authors; we wait and hope mobile operating systems make it better. There is also the whole world of software tools where what you build can be delivered both as a web app and native app, like Flutter.

Because they launch fast.

Native apps have a bunch of the resources required to run the app locally meaning they don’t need to get them from the network when opened.

Potential Solution: This is only partially true, to begin with. When you download the app, you’re downloading the resources just like the web does. The web caches resources by default, and has more advanced caching available through Service Workers. PWAs are competitive here. Native apps aren’t automatically faster.

Because it’s harder for users to block ads and easier to collect data.

There are all sorts of ad blockers for mobile.

But those work only in mobile web browsers, not native apps. If you’d like to block ads in native apps… too bad, I guess? If the point of the thing you are building is to show ads or track users, well, I guess you’ll do better with a native app, assuming you can get the same kind of traffic to it as you can a website (which is questionable).

I’m hesitant to put “because native apps are more secure” as a reason on this list because of the fact that, as a user, you have so little control over how resources load that it doesn’t feel like an increased security risk to me. But the fact that native apps typically go through an approval process before they are available in an app store does offer some degree of protection that the web doesn’t have.

Potential Solution: Allow ad/tracker blocking apps to work with native apps.

Because users stay logged in.

This is a big one! Once you’re logged in to a native app, you tend to stay logged in until you specifically log out, or a special event happens like you change your password on another device. Web apps seem to lose login status far more often than one might like, and that adds to a subconscious feeling about the app. When I open my native Twitter app, it just opens and it’s ready to use. If I thought there was a 30% chance I’d have to log in, I’m sure I’d use it far less. (And hey, that might be a good thing, but a business sure won’t think so.)

There is also a somewhat awkward thing with web apps in that, even if you’re logged in on your mobile devices primary browser, you won’t necessarily be logged into some other app’s in-app browser context — whereas, with native apps, they often intercept links and always open in the native app context.

Potential Solution: There isn’t any amazing solution here. It’s largely just trickery. For example, long cookie expiration dates (six months is about what you can get if you’re lucky, I hear). Or you can do a thing where you keep a JSON Web Token (JWT) in storage and do a rolling re-auth on it behind the scenes. There are some other solutions in this thread, many of which are a bit above my head. Making the log in experience easier is also a thing, like using oAuth or magic email links, but it’s just not the same as that “always logged in” feeling. Maybe smart browser people can think of something.

Because the apps can have that “native feel.”

Typically meaning: fast and smooth, but also that they look the way other apps on that platform look and feel. For example, Apple offers SwiftUI which is specifically for building native apps using pre-built componentry that looks Apple-y. Replicating all that on the web is going to be hard. You have to work your ass off to make it as good as what you get “for free” with something like SwiftUI.

Potential Solution: Mobile platform creators could offer UI kits that bring the design language of that mobile platform to the web. That’s largely what Google has done with Material, although the web version of it isn’t ready to use yet and is just considered a “planned” release.

Because they aren’t sharing a virtual space with competitors a tap away.

There is a sentiment that a web browser is just the wild west as you aren’t in control of where your users can run off to. If they are in your native app, good, that’s where you want them. If they are in a web browser, you’re sharing turf with untold other apps rather than keeping them on your holy ground.

Potential Solution: Get over it. Trying to hide the fact that competitors exist isn’t a good business strategy. The strength of the web is being able to hop around on a shared, standardized, open platform.

Because they get the full feature set of APIs.

All the web can hope for is the same API access as native apps have. For example, access to a camera, photos, GPS, push notifications, contacts, etc. Native apps get the APIs first, then if we’re lucky, years later, we get some kind of access on the web.

Developers might literally be forced into a native app if a crucial API is missing on the web.

One big example is push notifications. Are they generally annoying? Yes, but it’s a heavily used API and often a business requirement. And it makes plenty of sense for lots of apps (“Your turn is coming up in 500 feet,” “Your driver is here,” “Your baggage will be arriving on carousel 9,” etc.). If it’s crucial your app has good push notifications, the web isn’t an option for your app on iOS.

Potential Solution: The web should have feature parity with device APIs and new APIs should launch for both simultaneously.

Because there is an app store.

This is about discoverability. Being the one-and-only app store on a platform means you potentially get eyeballs on your app for free. If you make the best Skee-Ball game for a platform, there is a decent chance that you get good reviews and end up a top search for “Skee-Ball” in that app store, gaining you the majority share of whatever that niche market is. That’s an appealing prospect for a developer. The sea is much larger on the web, not to mention that SEO is a harder game to play and both advertising and marketing are expensive. A developer might pick a native app just because you can be a bigger fish right out of the gate than you can on the web.

And yet, if you build an app for listening to music, you’ll never beat out the major players, especially when the makers of the platform have their own apps to compete with. The web just might offer better opportunities for apps in highly competitive markets because of the wider potential audience.

Potential Solution: Allow web apps into app stores.

Because offline support is more straightforward.

The only offline capability on the web at all is via Service Workers. They are really cool, but I might argue that they aren’t particularly easy to implement, especially if the plan is using them to offer truly offline experiences for a web app that otherwise heavily relies on the network.

Native apps are less reliant on the network for everything. The core assets that make the app work are already available locally. So if a native app doesn’t need the network (say, a game), it works offline just fine. Even if it does need the network to be at its best, having your last-saved data available seems like a typical approach that many apps take.

Potential Solution: Make building offline web apps easier.

I’m a web guy and I feel like building for the web is the smart play for the vast majority of “app” situations. But I gotta admit the list of reasons for a business to go for a native app is thick enough right now that it’s no surprise that many of them do. The most successful seem to do both, despite the extreme cost of maintaining both. Like responsive design was successful in preventing us from having to build multiple versions of a website, I hope this complex situation moves in the direction of having websites be the obvious and only choice for any type of app.


, , , , ,

Wrangling Control Over PDFs with the Adobe PDF Embed API

By our last estimate, there are now more PDFs in the world than atoms in the universe (not verified by outside sources) so chances are, from time to time, you’re going to run into a PDF document or two. Browsers do a reasonably good job of handling PDFs. Typically, clicking a link to a PDF will open a new tab in your browser with custom UI and rendering per browser. Here’s the same PDF opened in Edge, Chrome, Firefox, and Safari, respectively:

As expected, each browser puts its own spin on things but one thing is consistent — all of them take over the entire viewport to render the PDF. While this is useful for giving the reader as much real estate to consume the PDF as possible, it would sometimes be desirable to have more control over the PDF experience. This is where the Adobe PDF Embed API comes in. The PDF Embed API is a free JavaScript library that lets you display PDFs inline with the rest of your content along with giving you control over the tools UI, supporting annotations and events, and more. Let’s walk through some examples of what it’s like to work with the library.

Getting a key

Before we begin, you’ll need to register for a key. If you head over to our Getting Started page, you’ll see a link to let you create new credentials:

If you don’t have an account with Adobe yet you’ll need to create one. You’ll be prompted to give the credentials a name and an application domain. While the name isn’t terribly important, the application domain is. The key you get will be restricted to a particular domain. You can only enter one domain here, so to start, you can use localhost or use cdpn.io as the domain if you want to try it on CodePen. If you want to use the API in both local and production environments, you can create multiple projects in the console or use HOSTS file configurations. (The ability to specify multiple domains for credentials is on the radar.)

Hit the lovely blue “Create Credentials” button and you’ll get your key:

If you’re curious and want to see what the Embed API can do right away, click on “Get Code Samples” which brings you to an interactive online demo. But since we’re hardcore coders who build our own editors before we go to work, let’s dive right into a simple example.

Building a demo

First, let’s build an HTML page that hosts our PDF. I’ve been a web developer for twenty years and am now an expert at designing beautiful HTML pages. Here’s what I came up:

<html>   <head></head>   <body>     <h1>Cats are Everything</h1>     <p>       Cats are so incredibly awesome that I feel like       we should talk about them more. Here's a PDF       that talks about how awesome cats are.     </p> 		     <!-- PDF here! -->      <p>       Did you like that? Was it awesome? I think it was awesome!      </p>   </body> </html>

I put it up a bit of CSS, of course:

A heading one that says Cats are Everything, followed by two short paragraphs about cats. The text is white against a green background.

I honestly don’t know why Adobe hired me as a developer evangelist because, clearly, I should be on a design team. Anyway, how do we get our PDF in there? The first step is to add our library SDK:

<script src="https://documentcloud.adobe.com/view-sdk/main.js"></script>

Now we need a bit of JavaScript. When our library loads, it fires an event called adobe_dc_view_sdk.ready. Depending on how you load your scripts and your framework of choice, it’s possible the event fires before you even get a chance to check for it.

We can also check for the existence of window.AdobeDC. We can handle both by chaining to a function that will set up our PDF.

if (window.AdobeDC) displayPDF(); else {   document.addEventListener("adobe_dc_view_sdk.ready", () => displayPDF()); }  function displayPDF() {   console.log('Lets do some AWESOME PDF stuff!'); }

Alright, so how do we display the PDF? To accept all the defaults we can use the following snippet:

let adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: "mypdf" }); adobeDCView.previewFile({   content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},   metaData:{fileName: "cat.pdf"} });

Let’s break that down. First, we create a new AdobeDC.View object. The clientId value is the key from earlier. The divId is the ID of a <div> in the DOM where the PDF will render. I removed the HTML comment I had earlier and dropped in an empty <div> with that ID. I also used some CSS to specify a width and height for it:

#mypdf {   width: 100%;   height: 500px; }

The previewFile method takes two main arguments. The first is the PDF URL. The PDF Embed API works with either URLs or File Promises. For URLs, we want to ensure we’ve got CORS setup properly. The second value is metadata about the PDF which, in this case, is the filename. Here’s the result:

Here’s a complete CodePen of the example, and yes, you can clone this, modify it, and continue to use the key.

You’ll notice the UI contains the same tools you would expect in any PDF viewer, along with things like the ability to add notes and annotations.

Note the “Save” icon in the figure above. When downloaded, the PDF will include the comments and lovely marker drawings.

Customizing the experience

Alright, you’ve seen the basic example, so let’s kick it up a bit and customize the experience. One of the first ways we may do that is by changing the embed mode which controls how the PDF is displayed. The library has four different ones supported:

  • Sized Container — The default mode used to render a PDF inside a <div> container. It renders one page at a time.
  • Full Window — Like Sized Container in that it will “fill” its parent <div>, but displays the entire PDF in one “stream” you can scroll through.
  • In-Line — Displays it in a web page, like Sized Container, but renders every page in a vertical stack. Obviously, don’t use this with some large 99-page PDF unless you hate your users. (But if you already display one of those “Sign up for our newsletter” modal windows when a person visits your site, or your site autoplays videos, then by all means, go ahead and do this.)
  • Lightbox — Displays the PDF in a centered window while greying out the rest of the content. The UI to close the display is automatically included.

To specify a different view, a second argument of options can be passed. For example:

function displayPDF() {   console.log('Lets do some AWESOME PDF stuff!');   let adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: "mypdf" });   adobeDCView.previewFile({     content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},     metaData:{fileName: "cat.pdf"}   },    {     embedMode: "IN_LINE"   });	 }

Note that in in-line mode, the height specified for your div will be ignored so that the PDF can stretch it’s legs a bit. You can view this version of the demo here: https://codepen.io/cfjedimaster/pen/OJpJRKr

Let’s consider another example – using lightbox along with a button lets us give the user the chance to load the PDF when they want. We can modify our HTML like so:

<html>   <head></head>   <body>     <h1>Cats are Everything</h1>     <p>       Cats are so incredibly awesome that I feel like       we should talk about them more. Here's a PDF       that talks about how awesome cats are.     </p> 		     <!-- PDF here! -->     <button id="showPDF" disabled>Show PDF</button>      <p>       Did you like that? Was it awesome? I think it was awesome!      </p>   </body> </html>

I’ve added a disabled button to the HTML and removed the empty <div>. We won’t need it as the lightbox mode will use a modal view. Now we modify the JavaScript:

const ADOBE_KEY = 'b9151e8d6a0b4d798e0f8d7950efea91';  if(window.AdobeDC) enablePDF(); else {   document.addEventListener("adobe_dc_view_sdk.ready", () => enablePDF()); }  function enablePDF() {   let btn = document.querySelector('#showPDF');   btn.addEventListener('click', () => displayPDF());   btn.disabled = false; }  function displayPDF() {   console.log('Lets do some AWESOME PDF stuff!');   let adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY });   adobeDCView.previewFile({     content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},     metaData:{fileName: "cat.pdf"}   },    {     embedMode: "LIGHT_BOX"   });	 }

There are two main changes here. First, checking that the library is loading (or has loaded) runs enablePDF, which removes the disabled property from the button and adds a click event. This runs displayPDF. Notice how the initializer does not use the divId anymore. Second, note the embedMode mode change. You can try this yourself via the Pen below.

You have more customization options as well, including tweaking the UI menus and icons to enable and disable various features:

adobeDCView.previewFile({ 	content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}}, 	metaData:{fileName: "cat.pdf"} },  { 	showDownloadPDF: false, 	showPrintPDF: false, 	showAnnotationTools: false, 	showLeftHandPanel: false });	

You can most likely guess what this does, but here’s a shot with the default options:

And here’s how it looks with those options disabled:

By the way, just so we’re clear, we definitely know that disabling the download button doesn’t “protect” the PDF seen here, the URL is still visible in via View Source.

Again, this is only a small example, so be sure to check the customization docs for more examples.

Working with the API and handling events

Along with customizing the UI, we also get fine grained control over the experience after it’s loaded. This is supported with an API that can return information about the PDF as well as the ability to listen for events.

Working with the API uses the result of the previewFile method. We haven’t used that yet, but it returns a Promise. One use of the API is to get metadata. Here’s an example:

let resultPromise = adobeDCView.previewFile({   content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},   metaData:{fileName: "cat.pdf"} }, { embedMode:"SIZED_CONTAINER" });	  resultPromise.then(adobeViewer => {   adobeViewer.getAPIs().then(apis => {     apis.getPDFMetadata()     .then(result => console.log(result))     .catch(error => console.log(error));   }); });

This returns:

{   'numPages':6,   'pdfTitle':'Microsoft Word - Document1',   'fileName':'' }

Along with API calls, we also have deep analytics integration. While the docs go into great detail (and talk about integration with Adobe Analytics), you can handle PDF viewing and interacting events in any way that makes sense to you.

For example, since we know how many pages are in a PDF, and we can listen for events like viewing a page, we can notice when a person has viewed every page. To build this, I modified the JavaScript, like so:

const ADOBE_KEY = 'b9151e8d6a0b4d798e0f8d7950efea91';  //used to track what we've read const pagesRead = new Set([1]); let totalPages, adobeDCView, shownAlert=false;  if(window.AdobeDC) displayPDF(); else {   document.addEventListener("adobe_dc_view_sdk.ready", () => displayPDF()); }  function displayPDF() {   console.log('Lets do some AWESOME PDF stuff!');   adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: "mypdf" }); 	   let resultPromise = adobeDCView.previewFile({     content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},     metaData:{fileName: "cat.pdf"}   }, { embedMode:"SIZED_CONTAINER" });	    resultPromise.then(adobeViewer => {     adobeViewer.getAPIs().then(apis => {       apis.getPDFMetadata()       .then(result => {         totalPages = result.numPages;         console.log('totalPages', totalPages);         listenForReads();       })       .catch(error => console.log(error));     });   }); 	 }  function listenForReads() { 	   const eventOptions = {     enablePDFAnalytics: true   }    adobeDCView.registerCallback(   AdobeDC.View.Enum.CallbackType.EVENT_LISTENER,   function(event) {     let page = event.data.pageNumber;     pagesRead.add(page);     console.log(`view page $ {page}`);     if(pagesRead.size === totalPages && !shownAlert) {       alert('You read it all!');       shownAlert = true;     }   }, eventOptions );  }

Notice that after I get information about the page count, I run a function that starts listening for page viewing events. I use a Set to record each unique page, and when the total equals the number of pages in the PDF, I alert a message. (Of course, we don’t know if the reader actually read the text.) While admiditely a bit lame, you can play with this yourself here:

const ADOBE_KEY = 'b9151e8d6a0b4d798e0f8d7950efea91';  //used to track what we've read const pagesRead = new Set([1]); let totalPages, adobeDCView, shownAlert=false;  if(window.AdobeDC) displayPDF(); else {   document.addEventListener("adobe_dc_view_sdk.ready", () => displayPDF()); }  function displayPDF() {   console.log('Lets do some AWESOME PDF stuff!');   adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: "mypdf" }); 	   let resultPromise = adobeDCView.previewFile({     content:{location: {url: "https://static.raymondcamden.com/enclosures/cat.pdf"}},     metaData:{fileName: "cat.pdf"}   }, { embedMode:"SIZED_CONTAINER" });	    resultPromise.then(adobeViewer => {     adobeViewer.getAPIs().then(apis => {       apis.getPDFMetadata()       .then(result => {         totalPages = result.numPages;         console.log('totalPages', totalPages);         listenForReads();       })       .catch(error => console.log(error));     });   }); 	 }  function listenForReads() { 	   const eventOptions = {     listenOn: [ AdobeDC.View.Enum.PDFAnalyticsEvents.PAGE_VIEW ],     enablePDFAnalytics: true   }    adobeDCView.registerCallback(     AdobeDC.View.Enum.CallbackType.EVENT_LISTENER,     function(event) {       /*        console.log("Type " + event.type);        console.log("Data " + JSON.stringify(event.data));       */       let page = event.data.pageNumber;       pagesRead.add(page);       console.log(`view page $ {page}`);       if(pagesRead.size === totalPages && !shownAlert) {         alert('You read it all!');         shownAlert = true;       }     }, eventOptions   );  }

How to learn more

I hope this introduction to the Embed API has been useful. Here are some resources to help you get deeper into it:

  • Start off by perusing the docs as it does a great job going over all the details.
  • We’ve got a live demo that lets you see everything in action and will even generate code for you.
  • If you have questions or need support, we’ve got a forum for questions and you can use the adobe-embed-api on StackOverflow as well.
  • If you need to work with PDFs at the server level, we’ve got the Adobe PDF Tools API as well as a crazy cool Adobe Document Generation tool you may like. These aren’t free like the PDF Embed API, but you can trial them for six months and test them out by signing up.

Lastly, we are absolutely open to feedback on this. If you’ve got suggestions, ideas, questions, or anything else, feel free to reach out!

The post Wrangling Control Over PDFs with the Adobe PDF Embed API appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , ,

Tools to Improve UX and Win Over Your Customers

An enjoyable user experience and high conversion rates go hand-in-hand. It makes sense then, that if you want to improve conversion rates, your first task is to improve user experience.

To improve UX, deeply understanding your users is non-negotiable. But speaking with customers one by one to figure out their pain points isn’t a feasible or fast strategy.

Which is why today, we’re showing you different tools you can use to learn more about your customers so you can make quick and impactful changes that improve UX and increase conversions!

Why your visitors aren’t converting

Before we talk about the different tools to help you understand your users, let’s look at five main reasons why you don’t see as many conversions as you’d like:

Your website is confusing to navigate: If navigating your way through a cornfield maze is easier than trying to get to the checkout page on your website, you have a problem. Users like it when they can get from point A to point B with no roadblocks or confusion.

You’re guessing what your visitors want (but don’t actually know): As someone who knows your product inside and out, it can be hard to take a step back to try and get into the mind of your visitors. Unless you’re gathering voice of customer data, any changes you make will be based on guesswork.

Your visitors are distracted: If your visitors are distracted by unimportant elements on your page, they’ll miss your CTA and won’t convert. Even worse, they’ll get frustrated by a lack of flow and leave your website (and maybe even head over to your competitor).

There are roadblocks you aren’t aware of: Using your website might be easy for you and your team—you helped build it, after all—but that bias makes it challenging to see roadblocks and other issues that could prevent your users from having an enjoyable experience.

Your users don’t trust your website: Things like poor design, spelling issues in your copy, and low-quality imagery can turn potential customers away.

Simply put, if your website is clunky, you’ll irritate your users, and they won’t convert. But when you understand what your visitors want—and why they want it—you’ll be able to build an experience they love.

4 tools to improve UX and conversions

At Hotjar, we want to help you make your users happy and avoid the above problems! Here are four tools you can use to improve your user experience, make your customers smile from ear to ear, and as a result, skyrocket your conversions!


Heatmaps are visual representations of your analytical data, organized so you can easily spot popular (and unpopular) areas of your website. With heatmaps, you can figure out which areas of your website contribute to a poor user experience.

You can use three types of heatmaps: scroll maps, click maps, and move maps.

Scroll maps
A scroll map

Scroll maps show you how far users scroll down your page. Red areas mean more visitors went to that part of your page, whereas the blue areas signal low activity. They can help you understand if users see key information.

Click maps
A click map

Click maps show an aggregate of where users click their mouse or tap the screen on desktop and mobile devices. Click maps help you understand if your CTAs are in the right place, if people are clicking on clickable items, and whether or not users are ‘rage clicking’ on your site.

Move maps
A move map

Move maps show where users move their mouse as they go through your page. Research suggests that mouse movement correlates with eye movement, helping you understand what people look at on your website.

You can use Hotjar’s Heatmaps to:

  • See whether important information is within the “hottest” areas of your heatmap (or if it’s being missed because it’s in areas where visitors aren’t scrolling to)
  • Decide where to move essential information based on where your users focus their attention
  • Spot where your visitors’ attention drops
  • A/B test to see how user behavior changes
  • Make sure clicks and taps happen on “clickable” elements
  • See how behavior changes on different devices (i.e., desktop, tablet, and mobile)


It’s easy to improve UX when you know how your users are really using your website. Recordings let you watch live playbacks of each user on your site so you can see exactly how your visitors navigate through your website, identify roadblocks, and make sense of your web analytics.

Recordings let you see mouse movement, scrolling, clicks, and keyboard strokes across multiple pages on your site.

You can use Hotjar Recordings to:

  • Make sense of your bounce rate by analyzing why your visitors are leaving your page(s)
  • Empathize with your visitors by understanding their roadblocks and frustrations on your website
  • Uncover what’s preventing your visitors from converting
  • Find bugs and see if something’s not working as planned (like pages that load differently on mobile versus desktop)
  • See how long it really takes for users to convert (and identify what’s preventing them from converting sooner)

Incoming Feedback

With Hotjar’s Incoming Feedback tool, you can eliminate the guesswork and get into your user’s mind by placing feedback widgets right on your website. Your visitors can tell you exactly what they like and dislike, and you can use that information to fix issues and provide more of what your users love.

Incoming Feedback widget

Users can also highlight certain elements of your site, so you don’t need to guess what they’re referring to—you’ll know precisely what they’re talking about!

You can use Hotjar’s Incoming Feedback to:

  • Get feedback on specific elements on your website and understand why your users like and dislike certain aspects of your site
  • Gather the emotions of your website visitors
  • Pinpoint exactly which areas are causing trouble for your users
  • Track changes over time to see if user experience is improving


Gathering voice of customer (VOC) data is easy with Hotjar Surveys. Hotjar has two types of surveys: on-site and off-site.

On-site surveys let you ask your users questions while on specific pages of your website. By asking open- or closed-ended questions (or a mix of both), you can get into the mind of your visitor and get valuable feedback to improve your website and increase conversions.

You can also use Hotjar’s on-site surveys to follow up on specific questions. For example, if you asked, “did you find what you were looking for today?” and someone clicked “no,” you can have a follow-up question asking them to explain why.

You can use Hotjar’s Surveys to:

  • Validate new product ideas
  • Understand why your visitors like or dislike aspects of your site
  • Find areas of your site that need fixing (for example, place an on-site survey on pages with high bounce rates to figure out why users are leaving)
  • Gather valuable insight from your users
  • Improve conversions through post-purchase surveys
  • how your customers you care about their input

Try Hotjar for free today!

All of Hotjar’s tools collect powerful data in ways everyone on your team will be able to understand. Using data to drive your decision-making process will steer you in the right direction, keep users happy, and improve your conversion rates!

Click to sign up and see how easy it is to understand your users with Hotjar!

P.S. To get you up to speed, we’ve put together checklists to help you improve user experience and increase conversions during your free Hotjar trial 💯

The post Tools to Improve UX and Win Over Your Customers appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , ,

More Control Over CSS Borders With background-image

You can make a typical CSS border dashed or dotted. For example:

.box {    border: 1px dashed black;    border: 3px dotted red; }

You don’t have all that much control over how big or long the dashes or gaps are. And you certainly can’t give the dashes slants, fading, or animation! You can do those things with some trickery though.

Amit Sheen build this really neat Dashed Border Generator:

The trick is using four multiple backgrounds. The background property takes comma-separated values, so by setting four backgrounds (one along the top, right, bottom, and left) and sizing them to look like a border, it unlocks all this control.

So like:

.box {   background-image: repeating-linear-gradient(0deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px), repeating-linear-gradient(90deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px), repeating-linear-gradient(180deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px), repeating-linear-gradient(270deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px);   background-size: 3px 100%, 100% 3px, 3px 100% , 100% 3px;   background-position: 0 0, 0 0, 100% 0, 0 100%;   background-repeat: no-repeat; }

I like gumdrops.

The post More Control Over CSS Borders With background-image appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , ,

Weaving One Element Over and Under Another Element

In this post, we’re going to use CSS superpowers to create a visual effect where two elements overlap and weave together. The epiphany for this design came during a short burst of spiritual inquisitiveness where I ended up at The Bible Project’s website. They make really cool animations, and I mean, really cool animations.

My attention, however, deviated from spiritualism to web design as I kept spotting these in-and-out border illustrations.

Screenshot form The Bible Project website.

I wondered if a similar could be made from pure CSS… and hallelujah, it’s possible!

See the Pen
Over and under border design using CSS
by Preethi Sam (@rpsthecoder)
on CodePen.

The principal CSS standards we use in this technique are CSS Blend Modes and CSS Grid.

First, we start with an image and a rotated frame in front of that image.

<div class="design">   <img src="bird-photo.jpg">   <div class="rotated-border"></div> </div>
.design {   position: relative;   height: 300px;   width: 300px; }  .design > * {   position: absolute;   height: 100%;   width: 100%; }  .rotated-border {   box-sizing: border-box;   border: 15px #eb311f solid;   transform: rotate(45deg);   box-shadow: 0 0 10px #eb311f, inset 0 0 20px #eb311f; }

The red frame is created using border. Its box-sizing is set to include the border size in the dimensions of the box so that the frame is centered around the picture after being rotated. Otherwise, the frame will be bigger than the image and get pulled towards the bottom-right corner.

Then we pick a pair of opposite corners of the image and overlay their quadrants with their corresponding portion in a copy of the same image as before. This hides the red frame in those corners.

We basically need to make a cut portion of the image that looks like below to go on top of the red frame.

The visible two quadrants will lay on top of the .rotated-border element.

So, how do we alter the image so that only two quadrants of the image are visible? CSS Blend Modes! The multiply value is what we’re going to reach for in this instance. This adds transparency to an element by stripping white from the image to reveal what’s behind the element.

Chris has a nice demo showing how a red background shows through an image with the multiply blend mode.

See the Pen
Background Blending
by Chris Coyier (@chriscoyier)
on CodePen.

OK, nice, but what about those quadrants? We cover the quadrants we want to hide with white grid cells that will cause the image to bleed all the way through in those specific areas with a copy of the bird image right on top of it in the sourcecode.

<div id="design">     <img src="bird-photo.jpg">     <div class="rotated-border"></div>      <div class="blend">       <!-- Copy of the same image -->       <img src="bird-photo.jpg">       <div class="grid">         <!-- Quadrant 1: Top Left -->         <div></div>         <!-- Quadrant 2: Top Right -->         <div data-white></div>         <!-- Quadrant 3: Bottom Left -->         <div data-white></div>         <!-- Quadrant 4: Bottom Right -->         <div></div>       </div>     </div>  </div>
.blend > * {   position: absolute;   height: 100%;   width: 100%; }  /* Establishes our grid */ .grid {   display: grid;   grid: repeat(2, 1fr) / repeat(2, 1fr); }  /* Adds white to quadrants with this attribute */ [data-white]{   background-color: white; }

The result is a two-by-two grid with its top-right and bottom-left quadrants that are filled with white, while being grouped together with the image inside .blend.

To those of you new to CSS Grid, what we’re doing is adding a new .grid element that becomes a “grid” element when we declare display: grid;. Then we use the grid property (which is a shorthand that combines grid-template-columns and grid-template-rows) to create two equally spaced rows and columns. We’re basically saying, “Hey, grid, repeat two equal columns and repeat two equal rows inside of yourself to form four boxes.”

A copy of the image and a grid with white cells on top of the red border.

Now we apply the multiply blend mode to .blend using the mix-blend-mode property.

.blend { mix-blend-mode: multiply; }

The result:

As you can see, the blend mode affects all four quadrants rather than just the two we want to see through. That means we can see through all four quadrants, which reveals all of the red rotated box.

We want to bring back the white we lost in top-left and bottom-right quadrants so that they hide the red rotated box behind them. Let’s add a second grid, this time on top of .blend in the sourcecode.

<div id="design">   <img src="bird-photo.jpg">   <div class="rotated-border"></div>        <!-- A second grid  -->   <!-- This time, we're adding white to the image quandrants where we want to hide the red frame  -->   <div class="grid">     <!-- Quadrant 1: Top Left -->     <div data-white></div>     <!-- Quadrant 2: Top Right -->     <div></div>     <!-- Quadrant 3: Bottom Left -->     <div></div>     <!-- Quadrant 4: Bottom Right -->     <div data-white></div>   </div>    <div class="blend">     <img src="bird-photo.jpg">     <div class="grid">       <!-- Quadrant 1: Top Left -->       <div></div>       <!-- Quadrant 2: Top Right -->       <div data-white></div>       <!-- Quadrant 3: Bottom Left -->       <div data-white></div>       <!-- Quadrant 4: Bottom Right -->       <div></div>     </div>   </div>  </div>

The result!

Summing up, the browser renders the elements in our demo like this:

  1. ​​At bottommost is the bird image (represented by the leftmost grey shape in the diagram below)
  2. ​​Then a rotated red frame
  3. ​​On top of them is a grid with top-left and bottom-right white cells (corners where we don’t want to see the red frame in the final result)
  4. ​​Followed by a copy of the bird image from before and a grid with top-right and bottom-left white cells (corners where we do want to see the red frame) – both grouped together and given the blending mode, multiply​.

You may have some questions about the approach I used in this post. Let me try to tackle those.

What about using CSS Masking instead of CSS Blend Modes?

For those of you familiar with CSS Masking – using either mask-image or clip-path – it can be an alternative to using blend mode.

I prefer blending because it has better browser support than masks and clipping. For instance, WebKit browsers don’t support SVG <mask> reference in the CSS mask-image property and they also provide partial support for clip-path values, especially Safari.

Another reason for choosing blend mode is the convenience of being able to use grid to create a simple white structure instead of needing to create images (whether they are SVG or otherwise).

Then again, I’m fully on board the CSS blend mode train, having used it for knockout text, text fragmentation effect… and now this. I’m pretty much all in on it.

Why did you use grid for the quadrants?

The white boxes needed in the demo can be created by other means, of course, but grid makes things easier for me. For example, we could’ve leaned on flexbox instead. Use what works for you.

Why use a data-attribute on the grid quadrant elements to make them white?

I used it while coding the demo without thinking much about it – I guess it was quicker to type. I later thought of changing it to a class, but left it as it is because the HTML looked neater that way… at least to me. 🙂

Is multiply the only blend mode that works for this example?

Nope. If you already know about blend modes then you probably also know you can use either screen, darken, or lighten to get a similar effect. (Both screen and lighten will need black grid cells instead of white.)

The post Weaving One Element Over and Under Another Element appeared first on CSS-Tricks.


, , , ,

A Bunch of Options for Looping Over querySelectorAll NodeLists

A common need when writing vanilla JavaScript is to find a selection of elements in the DOM and loop over them. For example, finding instances of a button and attaching a click handler to them.

const buttons = document.querySelectorAll(".js-do-thing"); // There could be any number of these!  // I need to loop over them and attach a click handler.

There are SO MANY ways to go about it. Let’s go through them.


forEach is normally for arrays, and interestingly, what comes back from querySelectorAll is not an array but a NodeList. Fortunately, most modern browsers support using forEach on NodeLists anyway.

buttons.forEach((button) => {   button.addEventListener('click', () => {     console.log("forEach worked");   }); });

If you’re worried that forEach might not work on your NodeList, you could spread it into an array first:

[...buttons].forEach((button) => {   button.addEventListener('click', () => {     console.log("spread forEach worked");   }); });

But I’m not actually sure if that helps anything since it seems a bit unlikely there are browsers that support spreads but not forEach on NodeLists. Maybe it gets weird when transpiling gets involved, though I dunno. Either way, spreading is nice in case you want to use anything else array-specific, like .map(), .filter(), or .reduce().

A slightly older method is to jack into the array’s natural forEach with this little hack:

[].forEach.call(buttons, (button) => {   button.addEventListener('click', () => {     console.log("array forEach worked");   }); });

Todd Motto once called out this method pretty hard though, so be advised. He recommended building your own method (updated for ES6):

const forEach = (array, callback, scope) => {   for (var i = 0; i < array.length; i++) {     callback.call(scope, i, array[i]);    } };

…which we would use like this:

forEach(buttons, (index, button) => {   console.log("our own function worked"); });

for .. of

Browser support for for .. of loops looks pretty good and this seems like a super clean syntax to me:

for (const button of buttons) {   button.addEventListener('click', () => {     console.log("for .. of worked");   }); }

Make an array right away

const buttons = Array.prototype.slice.apply(   document.querySelectorAll(".js-do-thing") );

Now you can use all the normal array functions.

buttons.forEach((button) => {   console.log("apply worked"); });

Old for loop

If you need maximum possible browser support, there is no shame in an ancient classic for loop:

for (let i = 0; i < buttons.length; ++i) {   buttons[i].addEventListener('click', () => {     console.log("for loop worked");   }); }


If you’re using jQuery, you don’t even have to bother….

$  (".buttons").on("click", () => {   console.log("jQuery works"); });

If you’re using a React/JSX setup, you don’t need think about this kind of binding at all.

Lodash has a _.forEach as well, which presumably helps with older browsers.

_.forEach(buttons, (button, key) => {   console.log("lodash worked"); });


Twitter peeps:

Also here’s a Pen with all these options in it.

The post A Bunch of Options for Looping Over querySelectorAll NodeLists appeared first on CSS-Tricks.


, , , , ,