Showing posts with label css. Show all posts
Showing posts with label css. Show all posts

Tuesday, June 26, 2012

CSS Opacity, IE Filter, Binary and Script Behaviors

(This article is about using IE specific "filter" to mimic the standard CSS attribute "opacity", this mimicry's consequence when the Binary and Script Behaviors setting is disabled in IE8 and IE7, and the workaround.)

CSS is not rocket science, but nonetheless difficult due to the infamous browser-compatibility issue. Any experienced web developer will tell you that CSS is a mess. The CSS standard doesn't look very complicate on paper, however, in reality, there are always some corner cases that break your UI in certain browsers (most likely IE). Some of such corner cases are well documented and mind-shared in the web developer community, however, some of them are not so well known, lurking there to ambush you.

I got ambushed today by such corner case. This corner case is so nasty that it cannot be reproduced in most browser settings. Here is the story:

Opacity is a standard CSS attribute invented to control transparency of HTML elements. However, not so surprisingly, IE under version 9 doesn't support "opacity". To fix that, most articles returned by Google search will recommend adding "filter" -- a IE specific attribute to mimic "opacity".

#trans {
  opacity: 0.4;
  filter: alpha(opacity=40); /* For IE8 and earlier */
}

Snippet 1

The above snippet I partially copied from w3schools.com (link) works in most browser settings. Actually, it works so well that most web developers won't have to worry about it in their lifetime.

My application is a third-party web app. It works in a tight security environment in which IT built up a fortress to fend off any possibly suspicious activity. One of such suspicious activities is using ActiveX.

"""
The Microsoft Windows Server 2003 Internet Explorer Enhanced Security Configuration component (also known as Microsoft Internet Explorer hardening) reduces a server’s vulnerability to attacks from Web content by applying more restrictive Internet Explorer security settings that disable scripts, ActiveX components, and file downloads for resources in the Internet security zone.
"""
(If you really want to read the full explanation, here is the original article)

This IE security setting is called "Binary and Script Behaviors" under the "ActiveX controls and plug-ins" category.


Figure 1

This setting is disabled in my case, which means using ActiveX is not an option. Now, you must wonder why this has anything to do with CSS opacity?

The "filter" attribute actually relies on ActiveX in order to mimic "opacity" in IE8 and below (discussed in stackoverflow). When the "Binary and Script Behaviors" setting is disabled, ActiveX became unavailable and the filter trick stopped working.

In Snippet 1, the filter is set to "alpha(opacity=40)", but when the "Binary and Script Behaviors" setting is disabled, filter will be ignored; and if background color is black, you will see a solid black instead of a semi-transparent black.


Figure 2

To reproduce this issue in IE8, first, you need to remove your app from the Trusted Sites (Figure 3). Then, disable "Binary and Script Behaviors" (See Figure 1).


Figure 3

The workaround is to set the "background-image" attribute to a semi-transparent PNG only for IE 7 to 8.

#trans {
  opacity: 0.4;
  /* filter: alpha(opacity=40); <-- Remove filter */
}
body.ie7 #trans, 
body.ie8 #trans {
  background: transparent url(/images/opacity40_black.png);
}
Snippet 2
The above CSS works with the following conditional body tags for marking IE versions.
<!--[if lt IE 7 ]><body class="ie6"><![endif]-->
<!--[if IE 7 ]><body class="ie7"><![endif]-->
<!--[if IE 8 ]><body class="ie8"><![endif]-->
<!--[if IE 9 ]><body class="ie9"><![endif]-->
<!--[if gt IE 9 ]><body class="ie10"><![endif]-->
<!--[if !IE]>--><body class="not-ie"><!--<![endif]-->
Snippet 3
To create a semi-transparent PNG, there are millions of PNG editing tools out there, but I found this free online generator is sufficient: Transparent PNG generator.

Tuesday, April 3, 2012

CSS3 Transition: Slideup Box (Take 2)

Demo and source code

This post is to propose a better solution for creating an expandable/slideup box. My previous implementation has a flaw -- the fixed "max-height" truncate part of the content when its height grows. This solution will resolve this issue.

First, we have the following markup.

<article>
  <h2>Click me to expand</h2>
  <div class="content_w">
    <div class="content">
      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do 
      eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut 
      enim ad minim veniam, quis nostrud exercitation ullamco laboris 
      nisi ut aliquip ex ea commodo consequat...
    </div>
  </div>
</article>

When users click inside an <article>, the content area will slide down (expand) or slide up (collapse). The title (<h2>) will always be visible.

The idea is to wrap the content inside a wrapper (div.content_w). The wrapper will hide any overflown content. We will change the height of the wrapper to create a slide up/down effect.

article .content_w {
  overflow: hidden;
  height: 0;
}
article .content_w.transition {
  -webkit-transition: height 0.5s;
     -moz-transition: height 0.5s;
       -o-transition: height 0.5s;
          transition: height 0.5s;
}

The wrapper needs to have "overflow: hidden" in order to clip the overflown content. We set "height: 0" to collapse the box initially.

The transition will take effect on the wrapper's height. When the box needs to be collapsed, we set the wrapper's height to 0. CSS3 transition will smoothly slide up the box. When expanding (sliding down), we set the wrapper's height back to the height of its enclosed content.

$('article').on('click', function() {
  slide($('.content', this)); 
});

function slide(content) {
  var wrapper = content.parent();
  var contentHeight = content.outerHeight(true);
  var wrapperHeight = wrapper.height();

  wrapper.toggleClass('open');
  if (wrapper.hasClass('open')) {
    setTimeout(function() {
      wrapper.addClass('transition').css('height', contentHeight);
    }, 10);
  }
  else {
    setTimeout(function() {
      wrapper.css('height', wrapperHeight);
      setTimeout(function() {
        wrapper.addClass('transition').css('height', 0);
      }, 10);
    }, 10);
  }

  wrapper.one('transitionEnd webkitTransitionEnd transitionend oTransitionEnd msTransitionEnd', function() {
    if(wrapper.hasClass('open')) {
      wrapper.removeClass('transition').css('height', 'auto');
    }
  });
}

The trick is that we don't want to keep a fixed height on the wrapper when it finishes expanding. A fixed height will clip its content when it grows, or leave unnecessary space at the bottom when the content shrinks. To fix that, we need to set height back to "auto" in order to "relax" the height. However, setting "height: auto" on HTML elements with CSS3 transition will make the transition have no effect. We have to remove transitions before setting "height: auto".

Demo and source code

Tested in Chrome, Safari, FireFox, and Opera Mobile Emulator


Saturday, March 31, 2012

Sliding / Expandable / Collapsible Box with max-height CSS Transition

Note


An improved solution can be found in this post.








Source code and demo

One common CSS3 Transition is to slide up (collapse) and slide down (expand) a box by manipulating its "height" attribute, e.g. changing 400px to 0. However, when either height is set to "auto", the transition won't work anymore. This topic has been discussed here.

The solution is to change the "max-height" instead of "height". Max-height is a CSS attribute supported in almost all modern browsers (see compatibility chart here). It defines the maximum height of an element. We can use it to "shrink" a box by setting max-height to 0, or expand a box by restoring its original height. In order to restore the original height, we need to retain the computed height of the box content.

Adam at stackoverflow.com provided a solution inspired by this same idea. I simplified the solution by removing some of the JavaScript code.

HTML markup


Here I create an item (div.item) with a title (<h2>) and content area (div.content). I want to make the content area slide down (expand) or slide up (collapse) once the item is clicked. The title will always be visible.

<div class="item">
    <!-- Title -->
    <h2>Click me to expand</h2> 

    <!-- Content wrapper -->
    <div class="content_w"> 

        <!-- Content -->
        <div class="content"> 
            Lorem ipsum dolor sit amet, consectetur adipisicing elit, 
            sed do eiusmod tempor incididunt ut labore et dolore 
            magna aliqua. Ut enim ad minim veniam, quis nostrud 
            exercitation ullamco laboris nisi ut aliquip ...
        </div>
    </div>
</div>

I put the content div inside a wrapper (div.content_w). Instead of changing the max-height of the real content div, we change the wrapper's. This way, we can achieve the sliding up/down effect while still retaining the computed height of the real content div.

Style sheet


Here is the style sheet. Please notice that the max-height and transition are set on the content wrapper rather than the content itself. The content will remain unchanged no matter if the wrapper is collapsed or expanded. The content wrapper needs to have "overflow: hidden" in order to hide its contained content when the wrappers's height becomes less than the content's.

.item {
    width: 400px;
}

/* Content wrapper */
.content_w {
    overflow: hidden;
    max-height: 0;
    -webkit-transition: max-height 0.5s;
       -moz-transition: max-height 0.5s;
         -o-transition: max-height 0.5s;
            transition: max-height 0.5s;
}

JavaScript


With a little help from jQuery, I toggle the max-height between 0 and the content height based on the "open" class which I use simply to mark the expanded and collapsed state.

(function($) {

  // max-height transition. 
  // Inspired by http://jsfiddle.net/adambiggs/MAbD3/
  function toggleContent($contentWrapper) {
    // Get the computed height of the content
    var contentHeight = $('.content', $contentWrapper).outerHeight(true);

    // Add or remove class "open"
    $contentWrapper.toggleClass('open');

    // Set max-height
    if ($contentWrapper.hasClass('open')) {
      $contentWrapper.css('max-height', contentHeight);
    }
    else {
      $contentWrapper.css('max-height', 0);
    }
  }

  // Listen to click events on the item element 
  $('.item').on('click', function(e) {
    e.preventDefault();

    toggleContent($('.content_w', this)); 
  });

})(jQuery);​

One thing to notice is that I didn't use jQuery to do the transition. The transition is done by CSS. jQuery is used only for selecting DOM elements, marking elements, and applying CSS styles. You can replace jQuery with any of your favorite JavaScript libraries.

Limitations


The max-height is set to the content height when it is expanded. So if the content changes or re-flows later, some content will be clipped. The extra code that Adam put there is to prevent this by setting max-height to a really big number at the end of the expand transition. However, if you need a simple slideup box whose content and layout won't change after expansion, then this solution should work fine for you.

An improved solution can be found in this post.

Friday, November 5, 2010

Often used CSS hacks

1. IE6 and IE7 targeting


#my_element {
    color: #999;  /* Targets all browsers */
    *color: #999; /* Targets IE7, IE8 compatibility view, and below */
    _color: #999; /* Targets IE6 and below */
}

I'm aware of the "* html" hack (star html hack see more) and IE if declarative. But I personally prefer this trick. It's pretty self explanatory and easy to maintain (not to have more than one CSS files or rules).


2. Min height


We want to have a content with 50-pixel minimum height.

.content {
  height: auto !important;  /* All browsers except IE6 will honor !important */
  min-height: 50px;
  height: 50px;
}

IE6 will ignore the !important declaration, and it doesn't support the min-height CSS attribute, so the above CSS rule for IE6 is effectively same as "height: 50px". In IE6, if content has taller height than its container, the container's height will automatically expand. This makes min-height effect works on IE6.

I've run into cases where the above hack doesn't work on IE6 or IE7. You might need to add the following attributes to the above CSS rule:

*overflow: visible;       /* For IE7 and below */
*zoom: 1;                 /* For IE7 and below */


3. Float


The float CSS attribute itself is not sufficient to make float work as intended. You need this:

.float_left {
    float: left;
    display: inline;
    overflow: hidden;
    *zoom: 1;
}

If a floated element has a margin which goes the same direction as the float (e.g. left margin and left float), it will have its margin doubled in IE6. This might cause the floated element move out of its intended position. That's why the "display: inline" comes in, it fix this so called double float margin bug (Learn more about double float margin).

"overflow: hidden" is to clear floats so that the height of the container of the floated element will expand to contain the floated element. For more explanations, please see Techniques for Clearing Floats.

If you cannot have "overflow: hidden" for whatever reason, you can replace it with "overflow: auto".

"*zoom: 1" will make sure that the floated element's "hasLayout" property is properly set in IE6 and 7. A lot of IE related bugs are caused by the fact that some elements need hasLayout to be set. (See more about the Microsoft only hasLayout property.)


4. Horizontal centering


.h_center {
    margin: 20px auto;
    width: 300px;
}

The centered element must have a fixed width, and auto margins on left and right. Margins on top and bottom can be anything you want.

5. Vertical centering


HTML markup:

<div class="v_center_outer">
    <div class="v_center_inner">
        content goes here
    </div>
</div>

CSS:

.v_center_outer {
    position: absolute; 
    top: 50%;
} 
.v_center_inner {
    position: relative; 
    top: -50%
}

The idea is to push the outer DIV down by 50% of its parent height, then pull the inner DIV up by 50% of its own height.

To make the "position: absolute" work in the v_center_outer DIV, the container of the v_center_outer DIV needs to have a defined height (either in pixels or a percentage), and its "position" cannot be "static" which is a default value. Typically, you can choose "position: relative" or "position: absolute".

See more about Vertical Centering at here, and here.


6. More hacks & tricks


Wednesday, September 1, 2010

IE6 multi class CSS selector weirdness

1. Problem


Multi class CSS selectors such as ".green.bold" (no space between) are commonly used in modern web styling. However, whenever you have something fun to play, IE6 comes to ruin it.

.bold { font-weight: bold; }
.green.bold { color: green; }
.blue.bold { color: blue; }

<p class="bold green">
    Green and bold
</p>
<p class="bold blue">
    Blue and bold
</p>

In other browsers such as FireFox, the above CSS and HTML will be rendered like this:

Green and bold

Blue and bold

Now, be prepared for IE6 weirdness:

Green and bold

Blue and bold

That is how IE6 renders the above CSS. Let's take a closer look. Both lines are bold. That's right. However, the first line should be green instead of blue.

Although I don't have an official answer for this behavior, I found a theory to explain how IE6 CSS parser works in this case. This is just my theory. I haven't verified it against any W3C documents.

2. Theory


The way that IE6 parses these ".green.bold" and ".blue.bold" CSS selectors can be explained like this:

When IE6 runs to multi class selectors, e.g. ".green.bold", IE6 will only recognize the last class which is "bold". The preceding classes such as "green" will be ignored.

.green.bold { ... }

The above CSS rule will be parsed as

.bold { ... }

Now let's re-examine the CSS rules at the beginning of this article.

.bold { font-weight: bold; }
.green.bold { color: green; }
.blue.bold { color: blue; }

For IE6, this will be equivalent to:

.bold { font-weight: bold; }
.bold { color: green; }
.bold { color: blue; }

Please notice the last 2 lines. ".bold { color: green; }" precedes ".bold { color: blue; }", so blue overwrites green. However, "font-weight: bold" in the first CSS rule doesn't get overwritten due to the fact that later CSS rules don't define any font weights.

The above CSS can be further simplified to:

.bold { font-weight: bold; color: blue; }

With the "parsed" CSS, now we understand why IE6 rendered our CSS and HTML into two blue bold lines.

To prove my theory, I change the CSS rules a bit:

.bold { font-weight: bold; }
.green.bold { color: green; font-size: 24px; }
.blue.bold { color: blue; }

".green.bold" has font size set to 24px. Let's try to walk through it like what IE6 CSS parser works.

Step 1:
.bold { font-weight: bold; }
.bold { color: green; font-size: 24px; }
.bold { color: blue; }

Step 2:
.bold { font-weight: bold; color: green; font-size: 24px; }
.bold { color: blue; }

Step 3:
.bold { font-weight: bold; color: blue; font-size: 24px; }

Try this in IE6, the result will be like this.

Green and bold

Blue and bold

3. Solution


How do we fix this IE6 weirdness?

Because IE6 honors only the last class in a multi class selector, we can move the more specific class to last. So here we swapped "green" and "bold":

.bold { font-weight: bold; }
.bold.green { color: green; font-size: 24px; }
.bold.blue { color: blue; }

For IE6, this will be parsed as:
.bold { font-weight: bold; }
.green { color: green; font-size: 24px; }
.blue { color: blue; }

Now the result became:

Green and bold

Blue and bold

However, in real world, things won't be this simple. For example, this solution won't work in the 3-class case, e.g. ".class1.class2.class3". The styles that class2 defines will be lost. unless you copy the styles from class2 to class3, and thus equivalently make it a 2-class selector: .class1.class3

HTML Box model, IE, and 100% width

A HTML box has margin, border, and padding surrounding its content area.  According to the W3C specification, 'width' and 'height' CSS attributes only define the width and height of the content area, not the box itself.  The box's padding, border, and margin are not considered to be parts of the content area.

So the following CSS rule will render a 150-pixel wide and heigh 'myBox' DIV, although its CSS width and height are set to 100 pixels:

#myBox {
    width: 100px;
    height: 100px;
    padding: 10px;
    border: 5px;
    margin: 10px;
}

box width = width + 2 * (padding + border + margin) = 100 + 2 * (10 + 5 + 10) = 150

However, IE decides to have its own box model. IE includes padding and border (not margin) in width and height. So the above CSS rule will produce a 120-pixel wide and heigh box.

box width = width + 2 * margin = 100 + 2 * 10 = 120

Because the CSS width (100px) already includes padding (10px) and border (5px), the box's content area will be squeezed from 100 pixels to 70 pixels.

content width = width - 2 * (padding + border) = 100 - 2 * (10 + 5) = 70

This weird behavior can be fixed by declaring a doc type.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

The above doc type will force IE to honor the W3C standard, and apply CSS width only to the content area.

The standard W3C box model will give you headaches if you are not careful. For example, we want a box to have a 100% width and 10-pixel padding.

#myBox {
    width: 100%;
    padding: 10px;
}

This CSS rule makes the width of "myBox" 100% of its ancestor container. Let's say myBox's parent container has a 400-pixel box width. Since the myBox's width is 100%, it will be 100% of its parent's 400px width, so you might think that the width will be 400 pixels. Is it true? What about the padding?

The actual width will be 400px plus 20px.

box width = 100% of parent's box width + 2 * padding = 100% * 400 + 2 * 10 = 420

420 might not be what you want because it will be wider than its parent. In real word, this issue might screw up your layout. To fix this problem, add an inner DIV inside myBox.

<div id="myBox">
    <div id="innerBox">
    </div>
</div>

And divide the above CSS rule to two:

#myBox {
    width: 100%;
}
#innerBox {
    padding: 10px;
    /* Or you can use margin */
}

This will make sure that the innerBox has 10px paddings and still fits in the 400px wide myBox.

The rule of thumb is not to mix percents with paddings or margins.

Further reading on the box model: The Box Model Problem

Saturday, August 29, 2009

Syntax highlight in blogspot

In the "Layout" -> "Edit HTML" page, add the following code after <!-- end outer-wrapper -->, and save the template.

<!-- SyntaxHighlighter -->
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shCore.js'></script>
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushBash.js'></script>
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCss.js'></script>
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushJScript.js'></script>
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushPlain.js'></script>
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushPython.js'></script>
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushSql.js'></script>
<script type='text/javascript' 
src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushXml.js'></script>

<link href='http://alexgorbatchev.com/pub/sh/2.0.320/styles/shCore.css' 
rel='stylesheet' type='text/css'/>
<link href='http://alexgorbatchev.com/pub/sh/2.0.320/styles/shThemeDefault.css' 
rel='stylesheet' type='text/css'/>

<script type='text/javascript'>
SyntaxHighlighter.config.bloggerMode = true;
SyntaxHighlighter.all();
</script>
<!-- end SyntaxHighlighter -->

The SyntaxHighlighter library searches all <pre> tags and apply highlight styles according to their class names.

<pre class="brush: js">
var foobar = function() {
  var el = document.getElementById('something');
  if (el) {
    el.innerHTML = 'Found you!';
  }
}
</pre>

Here is the highlighted code:

var foobar = function() {
  var el = document.getElementById('something');
  if (el) {
    el.innerHTML = 'Found you!';
  }
}


References

- SyntaxHighlighter API
- Using SyntaxHighlighter on BLOGGER
- How to add syntax highlight to Blogger