Subtle UI Effects For Production Apps

This is part 1 in the series: Subtle UI Effects For Production Applications.

Interesting animations and effects can play a big role in the user experience for any app or website. Some effects are cool and eye catching when you first see them but more times than not they are too dramatic and quickly become distracting. Seemingly every wordpress theme these days has crazy parallax (killing the scroll fps) and icons popping in from all directions.

In building and continually improving an online experience for ordering custom frames, we believe that subtlety is the name of the game when it comes to most UI effects. Much of this knowledge has come after spending days trying to incorporate some cool new effect, only to realize much later that it has serious browser compatibility issues, or the animation adds too much jank to the scrolling performance.

This series will provide examples for UI effects that are good enough for apps in the real world with real users.

Rendering Performance

CSS animations can usually do a few things cheaply without making the rendering performance too janky:

  • Position - transform: translate()
  • Scale - transform: scale()
  • Rotation - transform: rotate()
  • Opacity - opacity: value

But this is really just a rule of thumb and can't be applied to every situation. I encourage you to read a really good, in depth article by Paul Lewis and Paul Irish about this topic: High Performance Animations.

It's a good idea to measure your site's rendering performance in Chrome dev tools even if you're staying within this safe zone of CSS animations.

To measure your scroll and rendering performance, open up Chrome Dev Tools, go to "Timeline" and click "record". You don't want to see many events hit the 30fps threshold.

In the not so distant past, animating one of these four properties could still have a bad impact on the user's scroll performance if there were multiple hover states being triggered during the mouse motion (below 30fps is considered to be janky performance and noticeably jerky to the user). However, Chrome and the other major browsers recently pushed updates so that mouse position events aren't sent to the browser while scrolling anymore. So this scrolling problem is no longer an issue for modern browsers.

If you want to make sure performance is decent in older browsers, you could use Javascript to detect the scroll event and append "pointer-events: none" to your body during the scroll. A good discussion on this technique can be found here. Be careful about missing clicks, however, with this technique. Most of these articles have examples with timeout delays of 500ms after the scroll stops, and any clicks during this delay will be lost. I'd recommend changing the delay to around 100ms.

One last note about animation performance before we get to the freebie UI effects. If you need to perform an animation outside of this CSS safe zone, stay away from CSS and do the animation with Velocity.js instead. You will save yourself a huge headache.

Image Hover Effects: Glossy Overlay

The following is an effect we use on all of our print detail pages. You can see it in the wild here. Or hover over the image below with your mouse. It uses an opacity transition to show a skewed linear gradient over the image (simulating a glare effect). The glare strength can easily be adjusted.

To add this effect to your site's images, simply add the class ".glossy_hover" to your <a> tags wrapping the images. The CSS for the effect is below.

.glossy_hover {
  position: relative;
  overflow: hidden;
  display: inline-block;
.glossy_hover::before {
  position: absolute;
  width: 100%;
  height: 100%;
  content: '';
  opacity: 0;
  transition: opacity 0.35s;
  background: linear-gradient(to right, transparent, white);
  transform: skewX(-45deg) translateX(-50%);
.glossy_hover:hover::before {
  opacity: 0.06;

Note: you should use something like Autoprefixer to avoid having to write the browser specific prefixes in your css. Also, make your css transitions as specific as possible. The transition is only being applied to the 'opacity' attribute in the css above.

Opacity changes, like this glossy hover example, can be animated with good performance. This is because changes in opacity can be offloaded to the GPU during the composition process.

Typography Effects: A Better Link Underline

When your site uses non-traditional fonts, the text-decoration: underline attribute of links often looks pretty bad. Mainly because the line width is too thick or there is a padding problem between the line and the font characters.

Also, quite often the underline brutally overlaps descenders (font characters like g, p, y that descend below the line) and each browser and OS has its own implementation for these underlines so design consistency is a nightmare.

A few different approaches have been tried to fix these underlines. People have tried using border-bottom, but this has as many style limitations as the original underline, and the line is too far below the characters. CSS box shadows are sometimes used for this effect but still have a large padding problem with no great solutions for getting the line closer to the text (e.g. messing with line-height and display: inline-block).

The best solution to this problem came from Marcin Wichary at Medium and it uses the background-image css property to generate these underlines. He wrote a fantastic blog post which I highly recommend. The background-image approach gives us tons of flexibility with regards to styling these underlines, and good UX can still be preserved.

Marcin's work was improved upon, in my opinion, by Adam Schwartz at His implementation and explanation can be found here: Smarter Link Underlines.

My implementation is a modification of Adam's technique and gives you flexibility for changing things like the underline color and hover color on a per link basis. All of the links on this blog use this code. The sass mixins are below.

@mixin textShadowToCropUnderline($color) {
  text-shadow: .03em 0 $color, -.03em 0 $color, 0 .03em $color, 0 -.03em $color, .06em 0 $color, -.06em 0 $color, .09em 0 $color, -.09em 0 $color, .12em 0 $color, -.12em 0 $color, .15em 0 $color, -.15em 0 $color;

@mixin linkUnderlines($backgroundColor, $color, $lineColor, $hoverColor) {
  color: $color;
  text-decoration: none;
  @include textShadowToCropUnderline($backgroundColor);
  background-image: linear-gradient($backgroundColor, $backgroundColor), linear-gradient($backgroundColor, $backgroundColor), linear-gradient($lineColor, $lineColor);
  background-size: .05em 1px, .05em 1px, 1px 1px;
  background-repeat: no-repeat, no-repeat, repeat-x;
  background-position: 0% 90%, 100% 90%, 0% 90%;

  &::selection {
    @include textShadowToCropUnderline(#e7f2fa);

  &::-moz-selection {
    @include textShadowToCropUnderline(#e7f2fa);
    background: #e7f2fa;

  &:before, &:after, *, *:before, *:after {
    text-shadow: none;

  &:visited {
    color: $color;
  &:hover {

To style your links, simply include the mixin. It's a good idea to give yourself the flexibility of omitting the underline if a 'without-underline' class is present, so you don't mess up things like buttons.

a:not(.without-underline) {
  //not a good idea to use link transitions with this method
  //vars to pass into mixin: $backgroundColor, $color, $lineColor, $hoverColor
  @include linkUnderlines(#fff, #2980b9, #bddbf1, #111);

Hope you enjoyed part 1 of Subtle UI Effects. Part 2 is coming soon...

Good design makes people happy. If you haven't yet seen our work, check out our custom gallery-quality frames and make your walls look awesome.


Recent Articles