CuttySSark

Navigate the wild waters of event-driven style changes.

A lightweight polyfill adding support for JS event triggering and matching in stylesheets using standard CSS2 selectors.

August 29nd, 2013 by Devin Ivy

Fork on Github

Often events are registered using JavaScript for the sole purpose of updating styles. Especially with the advent of CSS animations and other CSS3 syruppy sweetness, simple style changes can hold huge impact for user experience. The goal of CuttySSark is to allow the front-end developer to write compliant CSS that interacts seamlessly with the JS event model. And it's just a weeny little JS file.

Cutty pretty much only does two things.

  1. Registers events to apply a style block.
  2. Allows remote triggering by one DOM element on another.

A quick first look

Let's take a little peek just to see what this crazy thing looks like. Click away at the fine boxes below.

#big
#little

#big[on~="click|toggle"] #little {
    background: transparent;
}
        

This selector registers a click event on #big using Cutty's toggle option so that her child #little is given a transparent background. Thanks to the toggle option, the style is toggled on and off each click. What's great is that Cutty is not using JS to apply the style; it's simply making sure the selector #big[on~="click|toggle"] #little matches when it ought to.

Getting and using CuttySSark

You can download CuttySSark v1.0 right here or fork me on Github. The one dependency is jQuery 1.7+. Simply include both as such,

<script src="http://code.jquery.com/jquery-2.0.3.min.js" type="text/javascript"></script>
<script src="cuttyssark-1.0.js" type="text/javascript"></script>

I've also included on this page the animate.css library by Dan Eden to add some pizazz.

The syntax

The example above highlights the simplicity of Cutty. The syntax is pretty straightforward! You write standard CSS, but certain selectors have a new importance.

#cutty[on~="event|option"] .ssark {...}

The option is, well, optional. We'll go over those is a moment. As a side note, you can chain the on~= selectors to require that several events have taken place. For example, div[on~=click][on~=mouseout] will require that a div be clicked and the mouse leave it before the style is applied.

Events

An event can be any standard JS event name, sans the "on" prefix. Additionally, it may be a custom event fired by jQuery, or as you'll soon find, a custom event fired more explicitly by Cutty.

Options

1. toggle

The toggle option applies the style every other time the event is fired. Every time it's toggled off a second style can be applied by using off where the option parameter goes. So,
#big[~on="click|toggle"] #little matches on first, third, fifth click, etc.
#big[~on="click|off"] #little matches on second, fourth, sixth click, etc.

2. once

The once option guarantees the style will be applied only the first time the event is fired on a particular element.

3. clear

The clear option clears all other styles currently associated to the current element by Cutty.

4. state

The state option is similar to clear, but only clears other styles also registered with the state option.

5. default

Without any option parameter, e.g. img[on~=load], the corresponding style block is simply applied.

Remote event triggering

Cutty also allows an event fired on one element to trigger second event on some other elements.

selector1[on~="event1|trigger|event2(selector2)"] {}

When event1 is fired on an element matching selector1, event2 is triggered on all elements matching selector2. Chain this with media queries, and you're the juggernaut. Anybody want to integrate CuttySSark with Harvey?

Special (relative) selectors

There are a few special selectors that will match for selector2 relative to the element triggering the event. The selectors next, prev, and parent reference respectively the next sibling, previous sibling, and parent of the element matching selector1 which triggers the event. The next and prev selectors loop the siblings, so that the last sibling will select the first using next, and the first sibling will select the last using prev. Additionally, the self selector matches the element triggering the event.

Triggering "on page load"

There is a special event named cutty that is automatically triggered by the <body> element as soon as Cutty finishes processing stylesheets. So if you want to fire an event right away, you can do so with body[on~="cutty|trigger|someEvent(selector2)"].

Limitations and excluding stylesheets

One limitation of Cutty is that cross-origin linked style sheets cannot be read, so your browser will probably throw some sort of a security error if Cutty tries to touch one of them. Unfortunately that means that stylesheets hosted on a CDN are out as far as Cutty is concerned. We might be able to deal with this by enabling CORS and hacking Cutty a bit. In any case, to skip over a stylesheet, just include the data-cutty-bypass attribute on it and you'll be all set.

<link rel="stylesheet" href="http://cdn/steez.css" type="text/css" data-cutty-bypass />

Demos

Handling <img/> loads and errors

The Cutty Sark!
But Tam kent what was what fu' brawlie:
There was ae winsome wench and waulie
That night enlisted in the core,
Lang after ken'd on Carrick shore;
(For mony a beast to dead she shot,
And perish'd mony a bonie boat,
And shook baith meikle corn and bear,
And kept the country-side in fear);
Her cutty sark, o' Paisley harn,
That while a lassie she had worn,
In longitude tho' sorely scanty,
It was her best, and she was vauntie.
Ah! little ken'd thy reverend grannie,
That sark she coft for her wee Nannie,
Wi twa pund Scots ('twas a' her riches),
Wad ever grac'd a dance of witches!
Reload Page

Here's a simple, functional application of CuttySSark: using Cutty we can deal with images' styles when they load or error. Directly above is an image set to fade-in once it's fully loaded. And on the right (now looking quite empty, probably) there is a broken image set to fade-out when it errors. Of course, it may be more natural to just set the broken image to display: none, but hey, this is a demo!

                   
<img class="animated" src="thecuttysark.jpg" alt="The Cutty Sark!" />
<img class="animated" src="deadimg.jpg" alt="But Tam ... witches!" />
                

img[on~=load] {
    animation-name:fadeIn;
}

img[on~=error] {
    animation-name:fadeOut;
}
                

Three-flip notecard

                   
<div class="card first animated" id="card1">
    Tam
</div>
<div class="card animated" id="card2">
    o'
</div>
<div class="card animated" id="card3">
    Shanter
</div>
                

/* setup the styles for when the page loads */
.card {
    display: none
}

.first.card {
    display: block;
}

/* now setup the styles' different states
   and trigger some events */ 
.card[on~="click|state"] {
    display: none;
}

#card1[on~="click|trigger|show(#card2)"] {}
#card2[on~="click|trigger|show(#card3)"] {}
#card3[on~="click|trigger|show(#card1)"] {}

.card[on~="show|state"] {
    display: block;
    animation-name: flipInY;
}
                
Tam
o'
Shanter

Here's a nice first look at Cutty's capabilities. One upside here is that Cutty has allowed us also to write neat markup to acheive a flashy little effect. I don't even feel guilty! Here there are three sheets that rotate first to second to third, etc. The first two style blocks setup the style for when the page first loads, with only the first sheet displaying. Each sheet has a click and a show state. The click state makes the current sheet disappear and fires a show event on the next sheet. When the show event is fired on that sheet, it applies the style to display then animate it. The state option is used to make sure the click and hide styles never conflict with each other.

Infinite-pane lazy-loading carousel

This example fancies-up the previous demo by implementing relative selectors to allow for an arbitrary number of panes. Further, it has a more complicated event structure since the previous- and next-arrow controls must trigger the displaying and hiding of the proper panes. We also take advantage of the fact that an element with a CSS background image will not cause the browser to request the image if its parent has the style display: none. Which makes this carousel a lazy-loader!

In the first two style blocks we see that a pane will be visible exactly when it matches the display event. The third block says that when a hide event is triggered on a pane, it will clear any event-related styles (notably, it will clear any active display style). The fourth style block triggers the first pane to display as soon as Cutty has finished processing all stylesheets, using the special cutty event. Then we set up the events for hiding/displaying by the arrow controls. When the previous arrow is clicked it triggers an event called bubblePrev on the pane currently displaying. In turn, that event causes the pane to trigger hide on itself and display on its previous sibling, thus switching panes. The next-arrow control works similarly in the sixth block.


/*1*/
.pane {
    display: none;
}

/*2*/
.pane[on~=display]
{
    display: block;
    animation-name: fadeIn;
}

/*3*/
.pane[on~="hide|clear"] {}

/*4*/
body[on~="cutty|trigger|display(.first.pane)"] {}

/*5*/
.prev-arrow[on~="click|trigger|bubblePrev(.pane[on~=display])"] {}
.pane[on~="bubblePrev|trigger|hide(self)"] {}
.pane[on~="bubblePrev|trigger|display(prev)"] {}

/*6*/
.next-arrow[on~="click|trigger|bubbleNext(.pane[on~=display])"] {}
.pane[on~="bubbleNext|trigger|hide(self)"] {}
.pane[on~="bubbleNext|trigger|display(next)"] {}

                
<div> <span class="prev-arrow">◄</span> <span class="next-arrow">►</span> </div> <div> <div class="pane first animated"> <span style="background-image:url('Tim.jpg')"></span> <span style="background-image:url('Sam.jpg')"></span> </div> <div class="pane animated"> <span style="background-image:url('Devin.jpg')"></span> <span style="background-image:url('Brian.jpg')"></span> </div> <div class="pane animated"> <span style="background-image:url('Dan.gif')"></span> <span style="background-image:url('Justin.jpg')"></span> </div> <div class="pane animated"> <span style="background-image:url('Michael.jpg')"></span> <span style="background-image:url('Matt.jpg')"></span> </div> <div class="pane animated"> <span style="background-image:url('Chris.jpg')"></span> <span style="background-image:url('Max.jpg')"></span> </div> </div>

A slew of thoughts

CuttySSark is on the MIT license, so you can hack away at it!
Here are some hacking possibilities.

More stateful options e.g. named states, using Cutty for drag events, integrating with Harvey, supporting element-level media queries, building in keyboard events per key, chaining relative selectors, making a public refresh method to reprocess stylesheets (say, when an element is added to the page), test performance, build-in animation events, explore use of the animationend event for chaining animations, read Tam o' Shanter again.