Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Serving up different sized dynamic images based on device resolutions

Serving up different sized dynamic images based on device resolutions.

Jason and I were working on an app where we needed to render dynamic images of several different sizes for different mobile devices, depending on device resolution.

Our scenario was analogous to the following:

Let’s say you are an engineer for Twitter and you need to display a user’s avatar on their show page, but you need to serve a small, medium, or large image based on the resolution of the device accessing the page.
This is a potential candidate for CSS media queries, except that the image url needs to be obtained from our User object (in Ruby).
Let’s cross that bridge when we get there and spike on an initial proof-of-concept.

users#show view
<h2><%= @user.name %></h2>
<div class="avatar"></div>
# ...
stylesheet
/* Default - screens over 767px wide */
.avatar {
  /* ... */
  width: 220px;
  height: 220px;
  background: url('/assets/dfp/large.jpg');
}

/* Mobile Landscape */
@media only screen and (min-width: 480px) and (max-width: 767px) {
  .avatar {
    /* ... */
    width: 140px;
    height: 197px;
    background: url('/assets/dfp/medium.jpg');
  }
}

/* Mobile portrait */
@media only screen and (max-width: 479px) {
  .avatar {
    /* ... */
    width: 60px;
    height: 85px;
    background: url('/assets/dfp/small.jpg');
  }
}

Pretty straightforward stuff, especially if you’ve followed the Responsive Web Design (CSS media queries) trend.
The interesting thing to note here is that when putting the background image in the stylesheet, the asset does not get requested until you resize your browser to the size that uses that image.
Go ahead and load this page in a web browser, open up the network panel, resize the display, and watch the asset size change and different requests getting fired off. That’s exactly what we want!

Okay, great. But, although it would be glorious, not everyone is going to have Dog Fanny Pack for their avatar. We need to call some ruby method on some ruby object to get the image (e.g. user.avatar.path).
Not really something you can do from within the stylesheet. Alternatively, you wouldn’t want to move the background image reference to the view because then it gets loaded as soon as the page loads, regardless of the screen resolution.

To make this puppy (pun intended) dynamic, we create a little proxy. See below:

Routes
ResponsiveUserAvatar::Application.routes.draw do
  # ...
  match "users/avatars/:size.jpg" => "users#avatar"
end
Users controller
def avatar
  user = get_user
  size = params[:size]
  if user.present?
    send_file "#{Rails.root}/#{user.avatar.path(size)}"
  else
    render :status => :not_found, :text => "not found"
  end
end

We create an action, called avatar, on our users controller (you also create a separate avatars controller) that takes a size parameter. The avatar action first looks up the user (You don’t get to see how ’till later),
then it grabs the size from the url and renders the user’s avatar for that size.

Quick aside

You may notice this action isn’t really on the member (i.e. no user id), even though it should be. We’ll get to that in a second.
You may also be thinking of alternatives to using send_file. Jason and I experimented with redirects, but ran into caching issues. I am open to suggestions on these topics, so feel free to post feedback in the comments or submit a pull request to the demo app.

Back to business

Now all we have to do is call our proxy image/action in the stylesheet, see below:

users.css
/* Default - screens over 767px wide */
.avatar {
  /* ... */
  background: url('/users/avatars/large.jpg');
}

/* Mobile Landscape */
@media only screen and (min-width: 480px) and (max-width: 767px) {
  .avatar {
    /* ... */
    background: url('/users/avatars/medium.jpg');
  }
}

/* Mobile portrait */
@media only screen and (max-width: 479px) {
  .avatar {
    /* ... */
    background: url('/users/avatars/small.jpg');
  }
}

Coming clean…

So, I left out one minor detail… Okay major detail… It’s time to show the implementation of the “get_user” method.

users_controller
class UsersController < ApplicationController
  # ...
  private

  def get_user
    if request.env["HTTP_REFERER"].present?
      user_id = request.env["HTTP_REFERER"].match(//(d+)/?/)[1]
      user = User.find_by_id(user_id)
    end
  end
end

In our case we always had the user id in the page that used the avatar. If we tried to implement this on the listing page… Well it wouldn’t be pretty.

While this isn’t a silver bullet solution, it’s a cool concept and can be applied to many different applications. I call it the “responsive-css-background-fake-image-proxy” pattern.
Okay, maybe not, but to recap, the main components are:

  1. Use CSS media queries to target different device resolutions.
  2. Reference the background-image proxy url in the stylesheet, which only loads the image size needed.
  3. Use a fake image proxy to get your dynamic image.

Demo app and demo app code

Comments
  1. The idea is good but the reality is totally against it.

    The main problem here is that you move the CONTENT into the stylesheet.

    The avatar image is REALLY A CONTENT.
    What is wrong with using
    Avatar
    ?

    I you are lazy, then just write the helper: avatar_for(user, :medium).

    Apart from “theoretical” issues (that avatar is a content) I don’t even know where to start to looking at the issues that relate to the solution. Quick ones that pop up are:

    – You will no longer be able to debug images easily;
    – You will have issues debugging images referenced from different pages (multiple IDs)
    – You will have issues understanding WHY the images don’t appear on the customer side, while they DO on your;

    And I don’t even talk about:
    – Disabled HTTP-REFERRER
    – Possible cross-browser issues
    – Non-standard browsers

    Just to summarise, the whole idea of moving the CONTENT into CSS seems to be wrong IMHO. And this leads to the obscure solution here.
    When this happens, I usually go and reevaluate what I am doing.

    Cheers.

  2. Sorry I the previous comment treated img tag as HTML :)

    I meant to say there: What is wrong with using `Avatar`

  3. Alex Welch says:

    Dmytrii,

    I agree with you on several of your points. I even mention some of them in the post. You will notice that the tone of the post is not: this is the absolute way, but more of a cool little trick or proof-of-concept. Perhaps you didn’t read the entirety of the post?

    To answer your second comment: we don’t have the size (in your example: medium) in the view. We are deciding size based on screen resolution, hence the css media query portion.

    Thanks for your feedback,

    Alex

  4. Jeremy Jackson says:

    This is a really good alternative to some methods I’ve seen in the past (eg. determining if a request is coming from a mobile client in rack for instance).

    While it does have some potential downsides, with any issue you’re trying to solve it’s good to have as many approaches as possible, and this seems as viable if not better than many. And will likely be a good one to have in any tool belt.

    As always, you have to take into account what exactly your needs are when you approach a problem.

    @Dmytrii: While it’s often nice to have an image in the markup directly, there are times when it may not be the most optimal.. Issues with page/fragment caching can come into play for instance — generating caching for multiple device sizes is doable for sure, but if this fits your requirements, and saves you from having to serialize those fragments/pages to disk for each different image size, how much better might that be?

    That’s why I think it’s a good trick to keep in mind.

    captcha: suasively ;-P

  5. Theo Mills says:

    _We need to call some ruby method on some ruby object to get the image (e.g. user.avatar.path). Not really something you can do from within the stylesheet._

    In rails 3.1 (and I actually think I did this way back in rails 2.3) you __can__ do this from within the stylesheet. From the Rails Guide:

    _If you add an erb extension to a CSS asset, making it something such as application.css.erb, then helpers like asset_path are available in your CSS rules._

    Then you can change this:

    background: url(‘/users/avatars/small.jpg’);

    To this:

    background: url(‘< %= @user.avatar.path :small %>‘);

    I would probably create a view helper method to call in case a user isn’t present, but the point is that you can embed ruby in a css file.

    There are also special SASS helpers (image-path and asset-path e.g.) if you’ve got .scss files.

    See 2.2.1 in this section of the [Ruby on Rails Guide](http://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets).

  6. Alex Welch says:

    @Jeremy: you summarized my opinions exactly!

    @Theo: We don’t have access to @user in our stylesheet, even if we did the stylesheet will likely be cached on production. If you have some work-arounds for those issues i’d love to hear them.

    Thanks,

    Alex

  7. Theo Mills says:

    @Alex

    Good point about the caching, so your strategy does make a lot of sense. For static assets the erb interpretation is appropriate.

  8. Marcus Derencius says:

    What about having the avatar related css directly into the html?

  9. Alex Welch says:

    Yes! That is much better, so now our users show view is something like:

    < % content_for :head do -%>

    < % end -%>

    < %= @user.name %>

    < %= link_to("<< back", users_path, :class => “back”) %>

    We eliminate almost everything I felt dirty about in the original approach. I guess I initially thought that all three images would get requested on page load, but they don’t.

  10. Warner Chatriand says:

    CSS is designed primarily to enable the separation of document content (written in HTML or a similar markup language) from document presentation, including elements such as the layout, colors, and fonts.This separation can improve content accessibility, provide more flexibility and control in the specification of presentation characteristics, enable multiple pages to share formatting, and reduce complexity and repetition in the structural content (such as by allowing for tableless web design). .-

    With kind regards
    <http://www.caramoan.ph

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *