css sprite best practices

css sprite最佳实践(中文版)

(Updated on 2010-4-4, thank Scott Ballantyne )

The advantage of using the css sprite is to reduce a large number of http requests, so it makes the web page loaded much faster. I often find it it painful for me to compose a lot of images into one css sprite image and measure the x and y positions for each image.

Last year, I wrote a css_sprite gem, but to use it you need to define all the images you want to do the css sprite in the configuration file, and it is not easy to use. Because of this, recently I rewrote the css_sprite gem, it is not necessary to use configuration file any more by default, the new css_sprite gem follows the idea of Convention Over Configuration. Now the css_sprite gem can do the css sprite automatically.

First, let's look at the convention of the directory structrue.

The blue parts on the above image are the css_sprite directories according to convention. That means the directory whose name is css_sprite or css_sprite suffixed (e.g. another_css_sprite) needs to do the css sprite.

The green parts are images that need to be tranformed into the css sprite. Once you add images to the css_sprite directory or remove images, the css sprite operation will be automatically executed.

The red parts are automatically generated files. For each css_sprite directory, there is a css sprite image generated, combined by all the images under the css_sprite directory, and there is also a css or sass file generated according to the css_sprite image.

What about the generated css file?

.twitter_icon, .facebook_icon, .login_button, .logout_button {
  background: url('/images/css_sprite.png?1270170265') no-repeat;
}
.twitter_icon { background-position: 0px 0px; width: 14px; height: 14px; }
.facebook_icon { background-position: 0px -19px; width: 14px; height: 14px; }
.login_button { background-position: 0px -38px; width: 103px; height: 36px; }
.logout_button { background-position: 0px -79px; width: 103px; height: 36px; }

That means, the generated css file follows the naming convention: one image under the css_sprite directory corresponds to one class in the generated css file, the name of class is just the same as the name of image. The advantage of this is that developers only need to know what images are under css_sprite directory, then they can use the corresponding class names to display these images on the html page.

One difficulty that you may encounter is adding styles to a class that is being used by css sprite. Below is a description of how you might handle such an issue.

1. some related classes have some common styles. e.g. to buttons, you may apply them on input or a elements, in common you need to hide the text on input and a elements, hide the border and so on. So you should generate the common styles for these related classes. For example

.login_button, .logout_button {
  text-indent: -9999px;
  display: block;
  cursor: pointer;
  font-size: 0; # for ie
  line-height: 15px; # for ie
  border: 0; }

These styles should be added to the automatically generated css file accroding to user's customization. What customizations I always use are icon, logo, button and bg (abbr. for background).

2. the style for some specified class, e.g. define margin or float for login_button

.login_button {
  margin: 0 10px;
  float: left; }

These styles should be written in the user-defined css file, not the automatically generated css file.

Follow these rules, what you need to do is to put a new image into the css_sprite directory, then use the corresponding class name to display the image on html page. Generating css sprite image and css files are done automatically. Of course when I remove an image from the css_sprite directory, it is also removed from css_sprite image and css.

These are css sprite best practice I follow. Now it's time to see how to implement these in a rails application.

1. Install my css_sprite gem/plugin

sudo gem install css_sprite

Or

script/plugin install git://github.com/flyerhzm/css_sprite.git

Notice, css_sprite gem depends on the rmagick gem, so please make sure RMagick is successfully installed on your system.

Then add css_sprite gem in the environment.rb or Gemfile

2. Next make a directory whose name is css_sprite or ends with css_sprite (e.g. another_css_sprite) under public/images directory

3. If you install the css_sprite as a gem, you should add css_sprite task in Rakefile

require 'css_sprite'

If you install it as a plugin, you can skip this step

4. Let's start the css sprite automation

rake css_sprite:start

5. Put the images which need to do the css sprite under the css_sprite directory, then you will see the automatically generated css sprite image and css files. Now you can use the corresponding class name to display image on html. And don't forget include the stylesheet

<%= stylesheet_link_tag 'css_sprite' %>

Do you feel the you are saved from the dull css sprite job? Here are some additional tasks.

If you want to stop the css sprite automation, run

rake css_sprite:stop

If you want to restart the css sprite automation, run

rake css_sprite:restart

If you only want to run the css_sprite manually instead of automation, run

rake css_sprite:build

These are the default processes without configuration. If you want to use sass or you want to define common styles for some related classes, you need to define config/css_sprite.yml file

suffix:
  button: |
    text-indent: -9999px;
    display: block;
    cursor: pointer;
    font-size: 0;
    line-height: 15px;
    border: 0;
    outline: 0;

The effect of the above configuration file is to generate some common styles for all the images whose filename is button suffixed.

engine: sass
suffix:
  button: |
    text-indent: -9999px
    display: block
    cursor: pointer
    font-size: 0
    line-height: 15px
    border: 0
    outline: 0

The effect of the above configuration file is to generate a sass file, and generate some common styles for all the images whose filename is button suffixed. Please check the difference of the two configuration files, the content followed button: are copied to the automatically generated css or sass file. So please input the content according to the syntax of css or sass.

Notice, once you changed the configuration file, please make sure stop and start the css_sprite to take effects.

Finally, let's look at the automatically generated css file.

.login_button, .logout_button {
  text-indent: -9999px;
  display: block;
  cursor: pointer;
  font-size: 0;
  line-height: 15px;
  border: 0; }
.twitter_icon, .facebook_icon, .login_button, .logout_button {
  background: url('/images/css_sprite.png?1270170265') no-repeat;
}
.twitter_icon { background-position: 0px 0px; width: 14px; height: 14px; }
.facebook_icon { background-position: 0px -19px; width: 14px; height: 14px; }
.login_button { background-position: 0px -38px; width: 103px; height: 36px; }
.logout_button { background-position: 0px -79px; width: 103px; height: 36px; }

Don't hesitate to use the css_sprite to speed up your productivity, http://github.com/flyerhzm/css_sprite

Posted in  rails css rubygems


css sprite最佳实践

css sprite best practices (english version)

应用css sprite的好处在于可以大量减少http请求数,从而达到更快加载页面的效果。

但是对于像我这样的懒人,你让我每次都一个一个把图片copy到一个css_sprite图片里,还得量一下这个每个图片对应的x和y坐标,实在是一种折磨。

去年我就写了一个css_sprite的插件,但是由于需要在配置文件中定义所有需要组合在一起的图片,用起来还是很麻烦,不够傻瓜化。最近我把css_sprite插件重写了一遍,默认不需要使用配置文件,遵循rails的Convention Over Configuration的思想,可以做到全自动的css sprite操作。

首先,让我们看看目录结构的Convention是如何定义的

上图中蓝色部分就是Convention的css sprite目录,也就是在public/images目录下面的css_sprite目录或者以css_sprite结尾的目录(比如another_css_sprite),需要执行css_sprite操作。

绿色部分则是需要被css sprite的图片,你可以动态的在css sprite目录下面增加或删除图片,css sprite操作就会被自动触发。

而红色部分都是自动生成的,每个对应的css sprite目录,都会生成一个css sprite图片(图片内容为该css sprite目录下的所有图片组合),生成一个css sprite的css文件或者sass文件。

那么生成的css文件是怎么样的呢

.twitter_icon, .facebook_icon, .login_button, .logout_button {
  background: url('/images/css_sprite.png?1270170265') no-repeat;
}
.twitter_icon { background-position: 0px 0px; width: 14px; height: 14px; }
.facebook_icon { background-position: 0px -19px; width: 14px; height: 14px; }
.login_button { background-position: 0px -38px; width: 103px; height: 36px; }
.logout_button { background-position: 0px -79px; width: 103px; height: 36px; }

也就是说,它生成的css文件遵循如下一个命名规范:一个css sprite目录下的图片对应css里的一个class,图片的名字就是class的名字。这样的好处在于开发人员只需要知道css sprite目录下面有哪些图片,他就可以在html页面上面使用哪些class名字来显示这些图片,而且当css_sprite的算法发生变化的时候也不会对页面显示产生任何影响。

实际使用当中你可能会碰到这样的问题:你除了要使用这些css_sprite生成的class名来显示图片,还需要为它们定义额外的style,而这个可以分为两部分:

1. 一些相关的class有许多共同的style,比如对于button来说,你会把它应用到input或这a上面,一般就需要隐藏input或a标签上面的文字,需要去掉边框等等,所以你需要为这些相关的class生成共同的style,比如

.login_button, .logout_button {
  text-indent: -9999px;
  display: block;
  cursor: pointer;
  font-size: 0; # for ie
  line-height: 15px; # for ie
  border: 0; }

这些style应该根据用户的定制加入到自动生成的css文件中去。

2. 某个具体class应用的style,比如login_button需要定义margin或float

.login_button {
  margin: 0 10px;
  float: left; }

这些style应该写在用户自己的css文件中,而不应该加入到自动生成的css文件中去。

遵循以上的规则,我需要做的事情就是把一个新的图片扔到css_sprite目录下,然后在页面上使用这个图片对应的class name来显示这个图片,其它的事情(生成css sprite图片和css)都应该是自动完成的,当然当我把一个图片从css_sprite目录下面移除的时候,它也会自动从css_sprite图片和css中移除。听起来很不错吧!

上面就是我定义的css_sprite最佳实践,理论还不错,下面看看在rails项目中是如何使用的?

1. 当然是安装我的css_sprite的gem/plugin

sudo gem install css_sprite

或者

script/plugin install git://github.com/flyerhzm/css_sprite.git

注意,css_sprite依赖于rmagick gem,所以先请确保RMagick已经在你的系统中成功安装。

然后就是在environment.rb文件或者Gemfile文件中增加css_sprite gem

2. 在public/images目录下面生成css_sprite目录或者以css_sprite结尾的的目录(如:another_css_sprite)

3. 如果你是通过gem安装的css_sprite,那么需要在Rakefile中引用css_sprite的task

require 'css_sprite'

如果你是通过plugin安装的,可以跳过这一步

4. 开始css sprite自动化之旅

rake css_sprite:start

5. 把需要做css sprite操作的图片都放入css_sprite目录下面,然后你会看到自动生成的css_sprite图片和css文件,现在你就可以在html页面上引用图片对应的class名字来显示图片咯。对了,别忘了引用生成的css文件哦

<%= stylesheet_link_tag 'css_sprite' %>

是不是感觉从机械的css_sprite工作中解脱出来了呀。下面再介绍些额外的tasks

如果你想结束css_sprite自动化之旅,执行

rake css_sprite:stop

如果你想重新开始css_sprite自动化之旅,执行

rake css_sprite:restart

如果你不需要css_sprite自动化执行,而只想手动执行css_sprite操作,执行

rake css_sprite:build

上面这些流程都是在没有配置的默认情况下完成的,如果你需要使用sass,或者你需要为某些相关的class定义共同的style,只需要定义config/css_sprite.yml配置文件即可

suffix:
  button: |
    text-indent: -9999px;
    display: block;
    cursor: pointer;
    font-size: 0;
    line-height: 15px;
    border: 0;
    outline: 0;

上面这个配置文件的作用是为所有的文件名以button结尾的图片,生成一段共同的style。

engine: sass
suffix:
  button: |
    text-indent: -9999px
    display: block
    cursor: pointer
    font-size: 0
    line-height: 15px
    border: 0
    outline: 0

上面这段配置文件的作用是指定css_sprite自动生成sass文件,同时为所有的文件名以button结尾的图片,生成一段共同的style。注意两段配置文件的不同,button下面的内容都会完整的复制到自动生成的css或sass文件,所以你需要根据css和sass的语法来填入。

注意:当修改了配置文件,需要stop再start css_sprite才能生效。

最后,来我们来看一段自动生成的css文件吧

.login_button, .logout_button {
  text-indent: -9999px;
  display: block;
  cursor: pointer;
  font-size: 0;
  line-height: 15px;
  border: 0; }
.twitter_icon, .facebook_icon, .login_button, .logout_button {
  background: url('/images/css_sprite.png?1270170265') no-repeat;
}
.twitter_icon { background-position: 0px 0px; width: 14px; height: 14px; }
.facebook_icon { background-position: 0px -19px; width: 14px; height: 14px; }
.login_button { background-position: 0px -38px; width: 103px; height: 36px; }
.logout_button { background-position: 0px -79px; width: 103px; height: 36px; }

还等什么,赶紧来使用css_sprite来加快你的工作效率吧:http://github.com/flyerhzm/css_sprite

Posted in  rails rubygems css


类似facebook connect的方式验证twitter oauth

最近一个项目需要实现类似与uservoice一样的widget,也就是把一段javascript放到任何的网站上,然后动态生成一个iframe来显示我们网站的内容。但是碰到一个问题,在这个widget内需要允许用户使用twiiter oauth的方式登录,但是twitter oauth认证之后会使用window.top来redirect你的页面,这样会重置我们的widget,这显然是对用户很不友好的。同时,我发现facebook connect的方式可以很好的应用在我们的widget上面,因为它不会重新刷新页面。于是我想能不能用类似与facebook connet的方式,弹出一个页面来做twitter oauth的身份验证呢?显然,这是可行的。

其实很简单的,就是弹出一个页面,在那个页面上做twitter oauth的身份认证,在返回的时候记录session,同时关闭弹出的页面,javascript的代码如下

if (!TwitterConnect) {
  var TwitterConnect = {};
}
TwitterConnect.Twitter = new function() {
  var self = this;

  this.setOauthUrl = function(url) {
    self.oauth_url = url;
  }

  this.setCallback = function(callback) {
    self.callback = callback;
  }

  this.startTwitterConnect = function() {
    var popupParams = 'location=0,status=0,width=800,height=400';
    self._twitterWindow = window.open(self.oauth_url, 'twitterConnectWindow', popupParams);
    self._twitterInterval = window.setInterval(self.completeTwitterConnect, 500);
  }

  this.completeTwitterConnect = function() {
    if (self._twitterWindow.closed) {
      window.clearInterval(self._twitterInterval);
      eval(self.callback);
    }
  }
};

function _loadTwitterConnect() {
  if (document.getElementsByClassName == undefined) {
    document.getElementsByClassName = function(className) {
      var hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)");
      var allElements = document.getElementsByTagName("*");
      var results = [];

      var element;
      for (var i = 0; (element = allElements[i]) != null; i++) {
        var elementClass = element.className;
        if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass))
          results.push(element);
      }

      return results;
    }
  }

  var oauths = document.getElementsByClassName('twitter_oauth');
  for (var i = 0; i < oauths.length; i++) {
    var oauth = oauths[i];
    oauth.onclick = function() {
      TwitterConnect.Twitter.setOauthUrl(oauth.getAttribute('href'));
      TwitterConnect.Twitter.setCallback(oauth.getAttribute('onlogin'));
      TwitterConnect.Twitter.startTwitterConnect();
      return false;
    }
  }
};

_loadSuper = window.onload;
window.onload = (typeof window.onload != 'function') ? _loadTwitterConnect : function() { _loadSuper(); _loadTwitterConnect(); };

twitter oauth的链接需要这样写

<a class="twitter_button twitter_oauth" onlogin="window.location.href = '/tweets'" href="/oauth">Sign in with Twitter</a>

controller端的代码见http://www.huangzhimin.com/entries/171-oauth-for-twitter

这段javascript代码的作用是查找所有class为twiiter_oauth的链接,点击这个链接的时候进入oauth action,然后转发到twitter oauth的authorize_url,用户验证通过并返回之后,进入callback action,callback记录用户的session之后,返回一段self.close()的javascript关闭弹出页面,然后再执行twitter oauth链接的onlogin代码,这里的onlogin的跳转到'/tweets'页面。

用户使用起来就和facebook connect很像,所以我给它起名为twitter connect,并且发布到了github上面,项目地址如下:http://github.com/flyerhzm/twitter_connect,用起来很方便的哦。

Posted in  rails twitter javascript


使用princely生成pdf

Prince是一个将html和xml转换为pdf的程序,最突出的特点是prince能够根据css来格式化转换之后的pdf,这实在是太适合web程序员了。princely是一个基于prince的rails插件,使用起来也非常方便。

首先,下载prince并按照文档进行安装。

其次,安装princely

sudo gem install princely

接着就是在rails项目中生成pdf并供用户下载。定义一个名字叫download的action

def download
  # any logic

  respond_to do |format|
    format.html
    format.pdf do
      render :pdf => "pdf_file_name",
             :stylesheets => "pdf_css"
    end
  end
end

然后就是定义download.pdf.erb文件,它就和平时定义html.erb是一样的,样式由pdf_css.css决定。

这样,当用户点击一个链接进入这个download action,服务器就会在后台生成pdf,并发送response给用户,用户的浏览器就弹出下载的对话框。很简单吧

Posted in  rails ruby


shanghaionrails 3.20 活动

上周六参加了shanghaionrails的线下活动,活动地点就在云岭东路上的汇银铭尊,离我家很近,我就骑着自行车过去了。本次活动是由5173.com赞助的,场地非常不错,不禁感叹卖游戏装备的公司就是有钱啊。

言归正传,这次活动有四场演讲:

Leon和nouse带来的主题是"JS2应用"。JS2是factual公司编写的一个javascript框架,它提供一种以Ruby的语法来书写javascript代码的方法,实现了继承、mixins、更好的foreach、声明的getter/seeter方法等等。感觉和coffee-script蛮像的。

Jason带来的主题是“when ERP fell in love with rails”。主要是讲述了通过rails来开发ERP的经验,以及通过rails开发带来的高效率。

叶丁丁带来的主题是“NoSQL: Re-think about the world”。主要是介绍了很多NoSQL的产品和它们之间的比较。

最后我带来的主题是“Static Code Analysis for Ruby"。主要是通过rails_best_practices gem来介绍如何对ruby代码进行静态代码分析的。

最终的讨论环节也异常活跃,大家各抒己见。以后有机会一定要多参加这样的活动。

Posted in  presentation


Fork me on GitHub