What if the graphic design hue was adjustable in production?

In my current project, I needed to come up with a way to visually differentiate subdomain accounts. Adding rich theming support didn't make sense since:

  • End users are not responsible for the site design and hiring a graphic designer is low priority
  • The web application is for automating a workflow as much as possible. Less options and configuration means less maintenance and manual intervention

In order to meet these guidelines, I wondered about the possibility of asking end users to choose just one site color. Could the web application do something meaningful and seamlessly with that single choice?

Game Plan

Every time one of the stylesheets is linked to in the view:

  • open up the stylesheet
  • find all RGB color values and convert to match the requested color
  • save stylesheet to a cache directory

We'll be using the HSL color model. It breaks down a color into three components: hue, saturation, and lightness. Hue is what we're interested in. By shifting only a color's hue, we can, for instance, turn a red color to yellow without breaking the design legibility or contrast.

Conveniently, CSS3 allows us to adjust the hue by specifying an angle in degrees on a color wheel where red is both 0 and 360. Using this method, we only need to do basic substitution without any math conversions.

The bad news is HSL color values are not supported by older browsers or Internet Explorer 8. It is reassuring that modern versions of the other major web browsers (Safari, Firefox, Opera, and Chrome) do support the HSL model but we'll have to play the waiting game for legacy browsers to go out of use. To be safe, our color values will start off and end up as RGB values.

Finally, once we convert the stylesheets, we'll need to cache them somewhere. I'm storing them in #{Rails.root}/public/stylesheets/hue#{hue_value} because:

  • reuse: it is possible for several subdomain accounts to share the same hue value
  • it'll be served as a static file by Apache
  • free cache sweeping: upon deployment via Capistrano, old cached files are not carried over

Implementation Limitations

The colors in my stylesheets are RGB hex strings such as #1234AA and #123. There is no support for color names like 'red' in my implementation since I don't use them.

Another consideration are the graphic design constraints. Since only a dominate hue is allowed, image usage should be minimized or you'll have to worry about image conversions (not covered in this tutorial). Shoot for a minimalist design with blocks of solid color.

Implementation

install the Color gem. It is used to handle our color model and hue conversions.

# config/environment.rb config.gem 'color', :version => '1.4.0' rake gems:install

We're going to add several methods into ActionView::Helpers::AssetTagHelper. First, use alias_method_chain() to append our manipulation logic to run just before the original stylesheet_link_tag().

def stylesheet_link_tag_with_hue(*sources) options = sources.extract_options! sources.map! do |stylesheet_path| if stylesheet_path.starts_with?('/') || stylesheet_path.starts_with?('http') stylesheet_path else stylesheet_path = stylesheet_path + '.css' if File.extname(stylesheet_path) == '' ensure_stylesheet_for_hue(HUE, stylesheet_path) "hue/#{HUE}/" + stylesheet_path end end sources << options stylesheet_link_tag_without_hue *sources end alias_method_chain :stylesheet_link_tag, :hue

It'll run through the list of stylesheet sources. If any source is a candidate for hue conversion, ensure_stylesheet_for_hue() is called and the source is prepended with the hue directory path.

Here is where the file operations and hue conversions take place:

def ensure_stylesheet_for_hue(hue, path_suffix) source_path = Rails.root.join('public', 'stylesheets', path_suffix) destination_path = Rails.root.join('public', 'stylesheets', 'hue', hue.to_s, path_suffix) return nil unless File.exist?(source_path) return nil if File.exist?(destination_path) && Rails.env != 'development' File.open(source_path) do |file| output = file.read.gsub(/#([0-9a-f]{6}|[0-9a-f]{3})/i) do color = hsl_color_from_rgb_hex_string($1) color.hue = hue color.html end FileUtils.mkdir_p(File.dirname(destination_path)) File.open(destination_path, 'w') {|output_file| output_file.write(output)} end end

The color library doesn't have a constructor for our RGB color hex strings so we'll add one here.

def hsl_color_from_rgb_hex_string(hex) args = case hex.length when 3: [hex[0,1]*2, hex[1,1]*2, hex[2,1]*2] when 6: [hex[0,2], hex[2,2], hex[4,2]] end args.map! {|arg| arg.to_i(16)} Color::RGB.new(*args).to_hsl end

Results

Overall, I'm satisfied with the functionality. The biggest issue is the connotations we give to various colors and their shadings. For my site, it is hard to select an orange hue since dark orange doesn't look like orange. Also, it's not pink, it's lightish red. Some minor tweaking of saturation and lightness helps to make it work well with all hues though.

Design Ruby on Rails. June 01, 2009 - 10:10AM. 0 Comments