This post originated from an RSS feed registered with Ruby Buzz
by Vincent Woo.
Original Post: What if the graphic design hue was adjustable in production?
Feed Title: Undefined Range
Feed URL: http://www.undefinedrange.com/categories/ruby-on-rails.rss
Feed Description: Interesting things I experience with Ruby on Rails.
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.
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.