[philiptellis] /bb|[^b]{2}/
Never stop Grokking


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

Sunday, April 24, 2011

pngtocss and getting back to basics

A couple of days ago Nicole tweeted about the absence of a tool to convert images to gradients. I didn't see the tweet right away because I was asleep at the time, but Sergey retweeted it later under the @perfplanet account and I caught that.

My first thought was that there really must be something like that. Why not just use the gimp's colour picker or something. My second thought was, "what the heck are CSS gradients?" followed closely by, "I've never read a PNG in C before".

Back to basics

All of this brought up a sense of déjà vu back to 1999, to the birth of my first opensource project. That started out with someone on the ilug-bom mailing list asking how he could find out which server was running on a given host. I started to type out an explanation and then figured that it would take me less time to hack up a solution in perl, so I did, and httptype was born.

Yesterday was very similar, the only real difference being that this time I knew how to use version control, so I jumped in with:
mkdir pngtocss
cd pngtocss
git init
mkdir src
And then started on a bit of research. First to find out how to read in a PNG.

Reading in a PNG

libpng.org has a great book on using the libpng library. It's a long book and I'm a lazy dev, so I didn't read the whole thing. Instead I found the Chapter that dealt specifically with reading a PNG into memory. It turned out to be Chapter 13. Now when I say "I found", I really mean that Yahoo! found it for me.

Anyway, I read the doc and it was pretty straightforward, so I jumped right into writing code, but guess what? My C programming skills were rusty. My Makefile skills more so. Having man at hand helped things move along and I was soon up to speed.

Once I had an in-memory array of the image's pixels, I had to figure out how to write out the CSS.

CSS gradients

There were a few sites that helped with this, in particular CSS tricks. A few printfs later and I had simple gradients done.

The code was simple, and handled a very specific case. It didn't work with all possible gradients, and wouldn't be able to handle a sprite, but this is how projects grow. You don't try to solve everything at the start. You solve a use case and push it out there to find out what people want.

The last time around I pushed it out to the mailing list and got a lot of feedback, patches and even a mentor. This time I announced it on twitter and got a bunch of feedback and a ton of retweets.

The physics of gradients

Today I decided to look at multi-colour gradients. Not because anyone had asked for it, but because I thought it might be interesting to figure out where a colour shows up.

Now there are various ways to look at a gradient. If you think about it from a physics background, it's essentially a particle moving with constant velocity. Each pixel position is a unit of time and the colour value is the particle's position at that point in time. The position at any point of time is calculated by the second equation of motion:
s = ut + ½at²
For a simple two colour gradient, there's no acceleration, and the velocity u is the difference in the two colours divided by the number of pixels in that direction. This is the same as the difference in colour of adjacent pixels (colour differential per unit pixel).

The cool thing is that for CSS, you only need to specify the end points and it will fill in everything else.

Multi-colour gradients

When dealing with multi-colour gradients, you need to figure out where the stop points are. Once you know what the colour velocity at the start of the gradient, you keep iterating through the image until the velocity is no longer what you expect it to be. This is a colour stop. Note down the new colour and its position and restart.

To speed things up, rather than look at every pixel along the way, I exponentially increase the pixel index that I look at until I hit a bump and then backtrack till I find what I was looking for. A sort of binary search.

Putting it all together, I ended up with this example page which has gradient images and the equivalent CSS gradient side by side. There are still limitations, but none that I care to fix at the moment.

Other tools

Also of note is Alex Sirota's Gradient Editor as part of Colorzilla. It's completely web-based and can also take in a file upload and generate a gradient from that. It doesn't handle the rainbow gradient yet, but I suppose it won't be too hard to get around that.

pngtocss

In any case, pngtocss is on github at https://github.com/bluesmoon/pngtocss, feel free to fork it and submit patches. The code isn't pretty and isn't really commented either.

Saturday, April 09, 2011

overflow:hidden, border-radius and position:absolute

In my last post, I showed off some CSS fun with border-radius and overflow:hidden. Truth be told, that post started out about a bug with WebKit based browsers in relation to those attributes, but when I got down to writing the post, I couldn't reproduce the bug. Today, while trying to animate it, I managed to isolate the problem, and it turns out that I'm not the only one.

The problem in brief is that the contents of a container with overflow:hidden will still overflow its border if you have border-radius set and position set to anything other than static. Bug 50072 on WebKit describes a similar issue in more detail, and I've posted my comments at the bottom of that bug. I've created a minimal test case on JSFiddle.

The problem only shows up when you set the container's position style attribute to something other than static (which is the default).

I've tested in the latest versions of Firefox, Chrome, Safari and Opera, and Firefox is the only one that renders it correctly.

Now before you go asking me to test on the latest WebKit, rest assured that it's compiling as I write this. I'll try and submit a fix if it still exists and I can figure out what needs patching, but given that position:static works correctly, it may just be a matter of seeing what's different with those two flows.

Update 2016-10-28: This appears to be fixed on the latest versions of Chrome, Firefox, Safari and Opera:

Bug fixed version

Wednesday, March 30, 2011

overflow:hidden and border-radius

So what exactly needs to happen when you set overflow:hidden on a container element? At which pixel point do the element's contents get hidden? Is it at the padding, the inside of the border, the outside of the border, the outline or somewhere else?

The spec sort of states that it's at the border or padding (depending on background-clip):
Other effects that clip to the border or padding edge (such as ‘overflow’ other than ‘visible’) also must clip to the curve.
With that in mind, I decided to throw rounded corners into the fray and have some fun:

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. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
Caveat: Opera is broken.

Anyone wanna try a deathstar?

Saturday, January 29, 2011

Printing unicode characters in web documents

I often have to look up reference sites to find out how to write a particular character in HTML, JavaScript or CSS when that character isn't on my keyboard. This post should save me some searching time in future.

To type a character that's not on your keyboard, you need its unicode codepoint in decimal or hexadecimal. In the examples below, HH means two hexadecimal digits, DD means two decimal digits, HHHH is four hexadecimal digits, and so on. DD+ means two or more decimal digits, HH+ means two or more hexadecimal digits.

HTML

To type out unicode characters in HTML, use one of the following:
  • &#DDD+;
  • &#xHHH+;
eg:
Ɖ == Ɖ
Ɖ == Ɖ
‡ == ‡

JavaScript

To type out unicode characters in JavaScript, use the following:
  • \uHHHH
eg:
 == \u2021

CSS

To print out a unicode character using CSS content, use the following:
  • \HH+
eg:
 == \2021
(Note: the CSS example that I've used here only works in browsers that support the :before pseudo class and the content rule, but in general you can use unicode characters anywhere in CSS.)

URL

URL context is different from HTML context, so I'm including it here.

To print a unicode character into a URL, you need to represent it in UTF-8, using the %HH notation for each byte.
eg:
‡ ==  %E2%80%A1
л ==  %D0%BB
' ==  %39
This is not something that you want to do by hand, so use a library to do the conversion. In JavaScript, you can use the encodeURI or encodeURIComponent functions to do this for you.

End notes

Use escape sequences only in two cases.
  1. Your editor or keyboard doesn't allow you to type the characters in directly.
  2. The characters could be misinterpreted as syntax, eg < or > in HTML.

References and Further Reading

  1. List of Unicode Characters on WikiPedia
  2. UTF-8 on WikiPedia
  3. Unicode and HTML on WikiPedia
  4. JavaScript Unicode Escape Sequences on Mozilla Developer Network
  5. Richard Ishida. 2005. Using Character Escapes in Markup and CSS in W3C Internationalisation.

Tuesday, January 25, 2011

device-width and how not to hate your users

I've been catching up on my technical reading, and this weekend was spent on Responsive Enhancement1. I'd read about it before on Jeremy Keith's blog and his comments on proportion perfection over pixel perfection2 made me think. Finally, Kayla's report3 on Smashing Magazine about responsive web design coming up as I was thinking about making bluesmoon.info more mobile friendly is what prompted me to study it in detail.

I'm not going to go into the details of responsive enhancement, the references at the end of this article serve that purpose. This article lists what I think are best practices and my reasons for them.

@media queries

As a web designer or developer, you want your page to be easily viewable across different devices and screen sizes. It shouldn't matter whether your user uses a 21" desktop monitor, a 13" laptop, a 10" iPad or a much smaller smartphone. Responsive web design uses @media queries to change the layout of the page using CSS based on browser width. You might have CSS that looks like this:
/* Default wide-screen styles */

@media all and (max-width: 1024px) {
    /* styles for narrow desktop browsers and iPad landscape */
}

@media all and (max-width: 768px) {
    /* styles for narrower desktop browsers and iPad portrait */
}

@media all and (max-width: 480px) {
    /* styles for iPhone/Android landscape (and really narrow browser windows) */
}

@media all and (max-width: 320px) {
    /* styles for iPhone/Android portrait */
}

@media all and (max-width: 240px) {
    /* styles for smaller devices */
}
And yes, you could go smaller than that, or have intermediate sizes, but I'll cover that later.

viewports

Now this works reasonably well when you resize desktop browsers4, but not so much for mobile browsers. The problem is that mobile browsers (iPhone/Safari, Android/Chrome and Fennec) assume that the page were designed for a wide screen, and shrink it to fit into the smaller screen. This means that even though users could have had a good customised experience for their smaller devices, they won't because the device doesn't know about this5. The trick is to use Apple's viewport6, 7, 8 meta tag in your document's head in conjunction with @media queries9:
<meta name="viewport" content="...">
I've left the content attribute empty for now because this is where I see confusion... which is what we'll talk about now.
width=device-width
Most sites that I've seen advise you to set the content attribute to width=device-width. This tells the browser to assume that the page is as wide as the device. Unfortunately, this is only true when your device is in the portrait orientation. When you rotate to landscape, the device-width remains the same (eg: 320px), which means that even if your page were designed to work well in a 480px landscape design, it would still be rendered as if it were 320px.

It's tempting to use the orientation media query to solve this problem, but orientation doesn't really tell you the actual width of the device. All it tells you is whether the width is larger than or smaller than the device's height. As ppk points out5, since most pages tend to scroll vertically, this is irrelevant.

Use this if you use the same page styles in portrait and landscape orientation. Also note that using width=device-width is the only way to tell android devices to use the device's width12.
initial-scale=1.0,maximum-scale=1.0
Setting initial-scale=1 tells the browser not to zoom in or out regardless of what it thinks the page width is. This is good when you've designed your page to fit different widths since the browser will use the appropriate CSS rules for its own width, and initial-scale stops the zooming problem that we faced without the viewport meta tag.

Unfortunately a bug, or more likely a mis-feature, in mobile safari messes this up when a device is rotated from portrait to landscape mode. initial-scale is honoured only on full page load. On rotate from portrait to landscape mode, the browser assumes that the page width stays the same and scales accordingly (1.5) to make 320 pixels fit into 480pixels. However, as far as @media queries go, it reports a 480px width, and uses the appropriate CSS rules to render the page. This results in a page designed for 480px rendered scaled up 1.5 times. It's not horrible, but it's not desirable. Fennec claims8 that it does the right thing in this case. The Android emulator is impossible to work with and I haven't tested on mobile Opera yet.

To get around this bug, the pixel perfection camp suggests also setting maximum-scale=1. This stops the page zoom in on rotate, but it has the undesired side effect of preventing the user from zooming the page. This is a problem from the accessibility point of view. Zooming in is a very valid use case for users with bad eyesight, and in some cases, even users with good eyesight who just want a closer look at some part of your page. Do this only if you hate your users. It goes without saying that setting user-scalable=no should also not be used on most general purpose pages.

A better solution may be design your page to use the same styles in portrait and landscape orientation and set width=device-width. This way even if it does zoom, it will still be proportionate. See Lanyrd10 for an example of this design.
width=<actual width>
Some sites advise using a specific viewport width and designing your pages for that width. This is fine if you're building a separate page for each device class, but that doesn't flow with the concept of responsive design. Fixed width layouts are for print. The web is fluid and adapts to its users. Your site should too. Don't use this.
@media all and (device-width:480)
While this is a media query rather than an option to the viewport meta tag, I've seen it at various locations, and don't think it's the best option around. Here's why. According to the CSS3 media queries spec11, the device-width media feature describes the width of the rendering surface of the output device. For continuous media, this is the width of the screen. For paged media, this is the width of the page sheet size.

We're dealing with continuous media here (on-screen as opposed to printed), in which case the spec states that this is the width of the screen. Unless the browser window is maximised, this might be larger than the viewport with. My tests show that most desktop browsers treat device-width and width as synonyms. Mobile browsers seem a little confused on the matter. As far as the viewport meta tag goes, device-width is the width of the device in portrait orientation only. For a 320x480 device, device-width is always 320px regardless of orientation. For CSS media queries, however, device-width is the width of the screen based on its current orientation.

If you are going to use this, use it in conjunction with the orientation media feature. Never use max-device-width and min-device-width. It's better to use max-width and min-width instead. Also remember that device widths may change with newer models. You want your design to be future proof.

Intermediate widths

I'd mentioned above that you could design for any number of widths. The important thing is to test your page for different browser widths. This is fairly easy to do just by resizing your browser window. Test, and whenever you find your page layout break, either fix the layout for all widths, or build a new layout for smaller widths.

On bluesmoon.info, I change many parts of the page depending on page width. The default design (at the time of this article) has 5% empty space around the content. This is fine really wide screens (1152px or more), but as you get smaller, the empty space becomes a waste. Below 1152px, I shrink this to 2% and below 1024px, I get rid of it completely. You could say that my page content was really built for 1024px. This design also works for the iPad in landscape mode.

Below 1000px, all 3 column pages switch to a 2 column layout. Below 580px, I move the right column on all pages below the main content. All pages that initially had 3 columns now have 2 columns below the main content.

As we get smaller, I reduce the space used by non-essential content like the footer, the sidebars and the menu at the top, leaving as much space as possible for main content. Finally, when we get below 380px, the whole page turns into a single column layout.

This is of course, just an example. Your own site may have a layout that works perfectly at all screen widths, or you may need to design only two or three layouts. It's easy to test and design, so there's no reason not to. Designing for multiple widths took me just a couple of hours, and a lot of it was spent reading the articles below.

Recommendations

So finally, this is what I recommend.
  1. DO use the viewport meta tag
  2. DO use media queries to render your page appropriately for various widths ranging from under 200px to 1024px or more
  3. DO use width=device-width,initial-scale=1 in your viewport meta tag OR use width=device-width alone12.
  4. DO NOT use maximum-scale=1 or user-scalable=no
  5. DO NOT use width=<specific width>
  6. DO NOT use @media all and (*-device-width: xxx)

Remember that using initial-scale=1.0 throws you open to a zooming bug in mobile Safari. Push Safari to fix this bug. Finally, David Calhoun has a great summary13 of all options to the viewport meta tag, and alternate meta tags for older phones. Well worth a read. Also note that Mozilla's documentation8 of the viewport meta tag is far better than Safari's7.

Footnotes & References

  1. Ethan Marcotte. 2010. Responsive Web Design. In A List Apart #306. ISSN: 1534-0295.
  2. Jeremy Keith. 2010. Responsive Enhancement. In adactio.
  3. Kayla Knight. 2011. Responsive Web Design: What It Is and How To Use It. In Smashing Magazine.
  4. Webkit based desktop browsers re-render the page correctly as you resize the browser, however they have a minimum width of 385px (on MacOSX) and I was unable to shrink the browser below this. Firefox 4 re-renders the page correctly until the width gets too narrow to fit the navigation toolbar. At that point the viewport width stays fixed even if you shrink the browser. The page is re-rendered if you type something (anything) into the URL bar. Opera 10/11 re-render correctly at all sizes.
  5. Peter Paul Koch. 2010. A tale of two viewports — part two. In Quirksmode.
  6. Using the Viewport on Safari. In Safari Web Content Guide.
  7. The viewport meta tag. In Safari HTML Reference.
  8. MDC. 2010. Using the viewport meta tag to control layout on mobile browsers. In Mozilla Developer Network.
  9. Peter Paul Koch. 2010. Combining meta viewport and media queries. In Quirksmode.
  10. Willison & Downe. Lanyrd.
  11. Lie et al. 2010. Media Queries. W3C Candidate Recommendation 27 July 2010.
  12. If you design your page for the narrow view and expect it to scale when rotated, then use width=device-width and nothing else. If, instead, you design your page for either width, then use width=device-width,initial-scale=1. This is the only way to get the android browser to render a page with the intended width. Mobile Safari will render the page exactly as if initial-scale=1 were specified alone. You will still end up with the zoom on rotate bug.
  13. David Calhoun. 2010. The viewport metatag (Mobile web part I).

Wednesday, August 04, 2010

Site theme

Ten months down the line,
I still like this site's style,
I've tweaked it a bit,
But I gotta admit,
That bluesmoon did this one fine.


When I work on a site, I always start with the design first. I'm a visual developer, so I really need to see what things will look like when I'm done. I also need to know if I can stand the design enough to stare at it for days on end during development. The result is that I end up hating my design in a couple of days and change things around, most often it's the colours.

This has happened a lot with earlier versions of my site's style, but the version I have now is one I like. I first pushed it out standardised across all my subdomains back in November 2009, and now 10 months down the line, it still pleases me.

I like the colours. They don't shout out at me, but frame my content well. Important content is visible while unimportant meta information is dimmed out. I played around with the margins a lot to get it to work the way I want across devices. It's not ideal, but it gets the job done. I took inspiration for the fonts from several sites until I settled on what I use now.

There's still a few issues. Not everyone likes the tag cloud, but it's the best way for me to navigate my site, especially since google's site search doesn't seem to work. The home page looks terrible on my E71, and I should get to fixing that some day, but other than that, it's ok.

Monday, July 19, 2010

4.01 Strict — Don't mess with Stubbornella

Mixing CSS is pixel princess, but weigh her down, and you'll have to deal with... Stubbornella.
Don't mess with stubbornella

Saturday, April 10, 2010

Can a website crash your RHEL5 desktop?

A few months ago I started having trouble with my RHEL5 desktop when running Firefox 3.5. On a few websites, the entire system would crash pretty consistently. It took a long time, but I finally managed to find the problem and get a fix in there.

My solution is documented on the YDN blog. Please leave comments there.

Edit 2022-10-31: It looks like the YDN blog no longer has any posts, so I've pulled this off the Internet Archive and reposted it here:

On Ajaxian, Chris Heilmann recently wrote about a piece of JavaScript to crash Internet Explorer 6 (IE6). That's not something I worry about because I'm a geek and I've used a Linux-based operating system as my primary desktop for the last 10 years. I've kept my system up to date with all patches, never log in as root, and have a short timeout on sudo. I've believed that while a malicious website could possibly affect my browser (Firefox), it was unlikely to affect my OS. That was up until a few months ago, when I upgraded to Firefox 3.5.

I started noticing that a few websites would consistently cause my system to freeze and the bottom part of the screen would show pixmaps from all over the place. The system would stay this way for a few seconds, and then I'd be thrown off to the login screen. My error log showed that X.org had been killed by a segfault. At times the system would completely freeze and the only way to get it back was a hard reboot (yes, I tried pinging and sshing in first).

Yikes. This wasn't supposed to happen. Even worse, this meant that anyone who knew how to exploit this could cause my system to crash at will. On further investigation, it appeared that this problem showed up with sites that used jQuery or YUI, but it wasn't consistent. It also happened only with Firefox 3.5 or higher on Red Hat-based systems. Debian-based systems like Ubuntu didn't have any trouble.

I also found that we could consistently reproduce the problem with Yahoo! Search, which is where Ryan Grove and Sarah Forth-Smith came in to debug the problem. Even weirder was that my Gnome desktop would bleed through elements on the Search results page. Eventually we hit upon Bug 498500 on Red Hat's Bugzilla bug-tracking system.

I edited /etc/X11/xorg.conf and added Option "XaaNoOffscreenPixmaps" to the Device Section. I restarted X and started surfing. I surfed for several weeks and used Y! Search all the time. I also used a bunch of the other sites that caused the previous problems. I used sites with jQuery and YUI.

No more screen fuzz, no more freezes, no more crashes, and no more reboots.

I haven't investigated this further, but my best guess for what would have caused this problem is CSS sprites that are partially hidden, or elements with negative left margins. The former is a common performance optimization, while the latter is common for page accessibility. Both good things, so not something you'd want to change.

In any event, if you're like me and have a Linux-based desktop, and see a similar problem, it may be worth trying the preceding solution.

Note: The bug in question has been resolved by Red Hat.

...===...