Dynamic image loading in a responsive slideshow
Dynamically loading content, that is, serving different content for different purposes is one of the most difficult aspects of web design that I’ve encountered. Our internal website redesign included a billboard section with an image that spanned the entire width of the screen. Accomplishing this in code is fairly straightforward: simply add the large image to the markup at 100% and let the browser do its thing. Even when you have multiple images contained in a slideshow, things are still relatively straightforward. You can add a javascript library for your slideshow, load your slides (with or without headlines and other elements) and be done. Now you’re all set for a desktop, but what happens when you view these massive images on a mobile device? Even If you only had a couple slides, at desktop-width, those image sizes can be quite large. Very few people are going to wait for to load several megabytes of data, no matter how badass your slideshow is.
Enter dynamically loaded content
A quick solution to this whole mess is to remove the slideshow altogether on mobile devices. However, if you simply must have your slideshow on those devices, there is hope. We leveraged Drupal’s image styles, a responsive javascript slideshow, and some admittedly slightly messy CSS to achieve our goal. The result is dynamically sized images based on a device’s screen size. On large screens our image sizes can be megabytes; on small they can be less than 100k.
Diving into the details (and the code)
By default, we used Drupal’s image styles to scale the images. Doing this compresses the images after a user has uploaded them and therefore ensures we’re working with files sizes that aren’t through the roof. Alternatively, you could simply set the maximum upload size to something other than infinity. We then used the Flex Slider module which gave us a responsive slideshow and touch navigation right out of the box. To bring it all together, we used a view template in which we created three separate containers that held the three different image styles we created earlier. We then swapped out those divs in our media queries based on specific breakpoints.
Ye be warned, pitfalls abound
It’s time to get into the nitty gritty (the code), but before we do you’ll have to forgive me. This works. That doesn’t mean it’s perfect. Details on the less-than-ideal pieces later.
First, let’s check out the view template
Here’s where we’re setting the three divs that contain our image styles. For simplicity, I’ve excluded some of code that contains our headlines, buttons, and other text.
<div class="flex-small-box" style="display: none">
<div class="fleximage small" style="background-image:url()">
div>
div>
<div class="flex-medium-box" style="display: none">
<div class="fleximage medium" style="background-image:url()">
div>
div>
<div class="flex-large-box" style="display: none">
<div class="fleximage large" style="background-image:url()">
div>
div>
What we’re doing here is printing out the fields that hold our three image styles and hiding them by default. We do this to ensure the large images never load on small devices. Everything is hidden until we tell it to be visible. Not so bad, right? Just override your view template, and add some custom markup around the fields.
Now, let’s take a look at the CSS. Again, I’m only including the essential pieces of CSS. I’m using SASS here, so there aren’t any braces or semicolons.
.view-frontpage-gallery
position: relative
overflow: hidden
.flexslider
border: none
// Override default flexslider border
background: none
// Override default flexslider box shadow
@include box-shadow(none)
.flex-viewport
position: relative
width: 100%
.flex-large-box
display: block !important
.fleximage
// Don't use position absolute here to avoid breaking background size
height: 480px
background-repeat: no-repeat
// Stretch the image to the full width of the container
background-size: 100% auto
Where the magic happens
This is where we add our CSS based on specific break points by using media queries. Again, I’m using SASS to import stylesheets under my media queries. Alternatively, you could load the CSS directly under the query itself. There’s nothing really mysterious here… It’s actually far from magic.
@media only screen and (max-width: 480px)
@import 'small'
@media only screen and (max-width: 700px) and (min-width: 481px)
@import 'medium'
@media only screen and (max-width: 960px) and (min-width: 701px)
@import 'large'
// Large
.view-frontpage-gallery
.flexslider
width: 960px
// Prevent the image from scaling
.flex-nav-container
height: 290px
.flex-medium-box, .flex-small-box
display: none !important
.flex-large-box
display: block !important // Show the largest image
.fleximage
height: 320px
// Medium
.view-frontpage-gallery
.flexslider
width: 700px !important
.flex-large-box, .flex-small-box
display: none !important
.flex-nav-container
height: 230px
.flex-medium-box
display: block !important
.fleximage
height: 350px
// Small
.view-frontpage-gallery
.flex-medium-box, .flex-large-box
display: none !important
.flex-nav-container
height: 130px
.flex-small-box
display: block !important
.fleximage
display: block
height: 160px
width: 480px
There’s always a catch, isn’t there?
The catch to all of this is that it’s a little messy.
First, I’m using inline CSS in my view template which is not ideal. Overriding inline CSS often leads to adding ‘!important’ to other CSS rules which often leads to more overriding issues.
Second, because we’re using background images, I needed to tell the images’ parent container to be a specific height in order to show that image. This part isn’t pretty either because I had to adjust that height at every break point as the image resized. It’s also not best practice to set heights on page elements. Ideally, you want to the page elements to be vertically flexible.
Last, the positioning of the elements within the slideshow container can be quite unruly in their desire to follow the block-model. I had to do some interesting things to force them into the desired location and even then, there are visual bugs that rear their ugly heads on certain screen sizes.
The end, with room for improvement
That’s basically the method to the madness. In essence, I’m hiding and showing divs based on certain breakpoints and then adjusting the width and height of the those divs accordingly. What isn’t shown here is that I hide the navigation controls on small screens because flex slider provides swiping navigation out of the box. This helps de-clutter the slideshow on small screens.
In an ideal world, I’d have crystal clear code, beautifully aligned text and navigation on all screen sizes, and a much leaner solution but I decided to err on the user experience side which is to have dynamically sized images based on a user’s device. In the end, I can live with some messy code. What I can’t live with is a user that hates their experience so much that they never return. I’m reminded of a common phrase I’ve read recently which is, “Nobody wants to wait while they wait.”