Setting JavaScript functionality with CSS

12 Jan 2015 · by David DeSandro

One of Slick's hotter feature request is to disable Slick functionality at certain CSS breakpoints. This touches at a tricky issue: setting JavaScript functionality with CSS. Imagine this CSS pseudo-code:

/* default css, small devices like phones, less than 768px */

/* JS widget is enabled by default */

@media screen and ( min-width: 768px ) {
  /* larger devices: tablets and up */

  /* JS widget is now disabled */
}

Slick has an awesome feature where you can set variations of options for various breakpoints.

$('.responsive').slick({
  slidesToShow: 4,
  slidesToScroll: 4,
  responsive: [
    {
      breakpoint: 1024,
      settings: {
        slidesToShow: 3,
        slidesToScroll: 3
      }
    },
    {
      breakpoint: 600,
      settings: {
        slidesToShow: 2,
        slidesToScroll: 2
      }
    }
  ]
});

But this approach comes with issues. It works by setting style information in JavaScript, when it should be kept in CSS. Also, JavaScript can mis-interpret the window width, causing a mismatch between the breakpoint used in JavaScript versus CSS.

Isotope element sizing

With Isotope, I tried a technique to set options via the state of CSS. You can set the columnWidth by pointing to the size of a dummy element.

<div class="container">
  <!-- dummy element for size of columnWidth -->
  <div class="grid-sizer"></div>
  <div class="item"></div>
  <div class="item"></div>
  ...
</div>
/* default, 3 columns */
.grid-sizer, .item { width: 33.333%; }

@media screen and ( min-width: 768px ) {
  /* larger devices, 5 columns */
  .grid-sizer, .item { width: 20%; }
}
$('.container').isotope({
  itemSelector: '.item',
  masonry: {
    // set columnWidth by width of .grid-sizer
    columnWidth: '.grid-sizer'
  }
});

See the Pen Isotope - element sizing columnWidth by David DeSandro (@desandro) on CodePen.

This technique requires an extra element be added to the HTML, but it allows the styles to be kept in CSS. So if the user changes the breakpoint or the size of the columns, they only need to change it in one place.

Activating Flickity with conditional CSS

Back to the first problem: How can the widget be enabled or disabled with CSS? I could use a dummy element, like with Isotope, but I'd rather not require extra markup if it's not necessary.

I like Jeremy Keith's Conditional CSS technique. It works by reading the content of a pseudo-element :after.

@media all and (min-width: 45em) {
  body:after {
    content: 'widescreen';
    display: none;
  }
}
var size = getComputedStyle( document.body,':after' ).content;
if ( size.indexOf('widescreen') !=-1 ) {
    // do widescreen stuff
}

Try resizing the window on this CodePen to see this technique in effect.

This technique has been built into Flickity. I've added a watch option. It looks for :after content of its container element to be flickity. If so, Flickity is enabled. If not, Flickity is disabled.

See the Pen Flickity watch option by David DeSandro (@desandro) on CodePen.

Technically, Flickity is always 'on'. But when inactive, its only functionality is to watch and see if it should activate. I added extra logic so that Flickity can activate and deactivate itself (rather than destroy/create) so it can re-use the same instance and UI elements.

Fallbacks

This technique is not without its own challenges. It doesn't work in IE8, Android 2.3, and previous versions of Chrome. My current solution is to run a feature test if reading the :after content will work. If not, the user can set watch: 'fallbackOn' to enableFlickity in these cases, or stick with watch: true to disable them. There's also a possible collision with how .clearfix uses :after. After Flickity has been released and I get more input, this feature is likely to be revisited.

Previously

If you're just joining us, I'm making a new gallery library! The story thus far...

Flickity is up on GitHub. Follow along development!

The best time

8 Jan 2015 · by David DeSandro

Right now is the best time. I've been working on Flickity, a new creation, for the past several weeks. It's been a completely solo endeavor. Just me and the code.

The code is new and pristine. Each step forward is an act of creation — it's own reward. This is a time of self-actualization. I am working at my highest level of quality, outputting my highest quantities. Making something new, for yourself and by yourself, feels so satisfying. It redeems all the drudgery of programming and open-source development.

Once it's released, the nirvana is over. There will be issues, support requests, other people wanting other things. The project will have a weight I'll have to bear. I'll be its steward.

Last week, I was involved in a bower.io issue about CSS minfication saving 8kb. Eight kilobytes. Four different people disagreed with me over this minutia. It's not that someone is right and someone is wrong — It's about feeling empowered.

Programming can be miraculous. I've had strangers across the world reach out and lift up the work for the innate ideal of making something better. But this comes at a cost. Your creation is no longer your own. It belongs to everybody.

Full-time fizzy

6 Jan 2015 · by David DeSandro

Yesterday was my last at Twitter. Today is my first, full-time at Metafizzy.

When I started this business four years ago, it was an experiment and a side-project. It was based on the idea that code can be worth more than GitHub stars and Twitter favorites. I've spent my mornings, nights, and weekends developing it. Even with this limited amount of time, the business has grown well — well enough to be my main gig.

Just like when I started, there are plenty of uncertainties. Gone are the comforts of steady income, deferred management, and company benefits. It's all up to me now. Things are getting real.

In the face of these unknowns, I am thrilled for what's to come. The work on Isotope and Packery has been some of my proudest. With this experience, I'm already hard at work developing a new product, Flickity. I now have the time to properly manage issues and emails and old code. Expect new things to be made and old things to get better.

My heartfelt thanks goes out to you who have purchased a license. Your support in real, monetary terms has enabled me to take the next step in my career and the next step for Metafizzy. I hope to do great things with it.

— Dave

Metafizzy full-time

Wrapping around Flickity for infinite looping

21 Dec 2014 · by David DeSandro

Sliders like Jssor and Slick have the ability to wrap around their cells, making an infinite loop. Users can keep progressing through the gallery without hitting an end. While I'm not sure if this is a good UI pattern (an infinite loop could be disorienting), I imagine that this is a nice feature that people will ask for.


Here's a simple slider you can drag left and right.

See the Pen Wrap around demo 1 by David DeSandro (@desandro) on CodePen.

For this demo, there are only three cells. After cell #3, the next cell should be cell #1. One way to resolve this is to move cell #1 to the end of cell #3 when it's about to be shown. This could work, but it would require moving around all the cells and keeping track of where they are.

Another way is to clone cells so there's overlap to work with. Both Jssor and Slick use this technique.

See the Pen Wrap around demo 2 by David DeSandro (@desandro) on CodePen.

Now the slider can be repositioned when the overlap is visible. The slider position needs to be kept between cellWidth and -sliderWidth + cellWidth, or 0 and sliderWidth if you subtract cellWidth. This math makes use of the remainder operator %.

var modNum = ( ( num % max ) + max ) % max

I've come to learn that is is a modulo operation, which JavaScript lacks. This is a useful operation you might see used when calculating an angle, to keep it between 0 and 360.

// keep angle between 0 and 360
angle = ( ( angle % 360 ) + 360 ) % 360

The tracked position can move outside these bounds, but when it is applied to the rendered position of the slider, then it is wrapped.

See the Pen Wrap around demo 3 by David DeSandro (@desandro) on CodePen.

There's a lot more going on with Flickity — calculating selected cells and applying forces. But the same principle applies. Try flicking all the way around this demo.

See the Pen Flickity - wrap around by David DeSandro (@desandro) on CodePen.

Previously

If you're just joining us, I'm making a new gallery library! The story thus far...

Flickity is up on GitHub. Follow along development!

Making SVG buttons

20 Dec 2014 · by David DeSandro

Flickity needs buttons.

Flickity needs buttons

They need to be:

  • Able to be positioned
  • Size-able responsively
  • Style-able

For these reasons, I'm going with inline SVG. Images could work, but they add more file overhead to be managed. An icon font could work, but it's files, plus they don't scale responsively with percentage width. CSS shapes could work, but they would require more code to handle size and positioning. Using SVG ticks all the checkboxes (although they are not supported by IE8 and Android 2.3, but I'll get to that).

Inline SVG

Inline SVG is just markup. Most SVG examples have a bunch of attributes in <svg>, usually left overs from exporting out of Illustrator. Take the inline SVG example from CSS Tricks' Using SVG

<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   width="612px" height="502.174px" viewBox="0 65.326 612 502.174" enable-background="new 0 65.326 612 502.174"
   xml:space="preserve" class="logo">

Turns out, a lot of these attributes can be removed. version is not necessary. Inline SVG does not require xmlns. x="0" & y="0" are default values. I managed to whittle away every attribute except for viewBox.

<svg viewbox="0 0 100 100">
  <!-- The arrow shape is simple enough that the path is hand coded -->
  <path class="arrow" d="M 50,0 L 60,10 L 20,50 L 60,90 L 50,100 L 0,50 Z" />
</svg>

See the Pen inline SVG by David DeSandro (@desandro) on CodePen.

Bonus, <svg> elements are responsive size ready, as they fill the width of their containers when width is not set.

Creating SVG with JavaScript

For the library, the SVG will be created with JavaScript. This way, users don't have to be concerned with adding complex markup. Creating inline SVGs is similar to creating elements on a page. You have to use createElementNS instead of createElement, which requires a URI as its first argument.

// create svg
var svgURI = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS( svgURI, 'svg' );
// SVG attributes, like viewBox, are camelCased. That threw me for a loop
svg.setAttribute( 'viewBox', '0 0 100 100' );
// create arrow
var path = document.createElementNS( svgURI, 'path' );
path.setAttribute( 'd', 'M 50,0 L 60,10 L 20,50 L 60,90 L 50,100 L 0,50 Z' );
// add class so it can be styled with CSS
path.setAttribute( 'class', 'arrow' );
svg.appendChild( path );
// add svg to page
element.appendChild( svg );

See the Pen SVG created with JS by David DeSandro (@desandro) on CodePen.

Putting these arrows into buttons is a matter of CSS. The big advantage of using SVG is how flexible they are to be styled, sized and positioned — all with CSS.

See the Pen previous / next button positioning by David DeSandro (@desandro) on CodePen.

With some CSS trickery, you can get the buttons to be scale proportionally with the width of the gallery. Open this example up in a new window and resize it to see it in action.

See the Pen previous / next button positioning by David DeSandro (@desandro) on CodePen.

No SVG fallback

IE8 and Android 2.3 do not support inline SVG. For this use case, it can fallback to use the default centering of <button> text. The button text can use special characters for arrows. It's not as pretty, but it keeps the CSS & HTML clean.

See the Pen No SVG previous / next buttons by David DeSandro (@desandro) on CodePen.

Put it all together

Here's where I'm at. In addition to dragging & flicking the slider, you can click the previous/next buttons to advance the cells one by one.

See the Pen Flickity - with buttons by David DeSandro (@desandro) on CodePen.

Previously

If you're just joining us, I'm making a new gallery library! The story thus far...

Flickity begins

17 Dec 2014 · by David DeSandro

The initial demos used <canvas> to draw simple shapes on a screen. I was able to test out ideas and get a better feel for the user interaction. Now I'm ready to start developing the actual library.

I need a name. For now, let's go with Flickity.

I have a head start as I'm building on the experience of developing other libraries, like Isotope, Packery, and Draggabilly. They all have the same basic structure.

// initialize new Flickity instance
var element = document.querySelector('#gallery')
var flickity = new Flickity( element, {
  // options
})

The constructor has two parameters: element, the element that will hold the gallery and its cells; and options, and object with options. The Flickity constructor method takes care of these two parameters.

// Flickity constructor
function Flickity( element, options ) {
  // use element as selector string
  // so users can set `new Flickity('#container')`
  if ( typeof element === 'string' ) {
    element = document.querySelector( element );
  }
  this.element = element;
  // options, first set options from defaults
  this.options = U.extend( {}, this.constructor.defaults );
  // then overwrite with user-set options
  this.option( options );
  // kick things off
  this._create();
}

// default options
Flickity.defaults = {
  friction: 0.2
};

Flickity.prototype.option = function( opts ) {
  U.extend( this.options, opts );
};

With this generic setup, all the specialized functionality will go into the Flickity.prototype methods.

I am able to build a slide-able gallery by copy/pasting plenty of code from the other libraries and initial <canvas> demos. Isotope and Packery work with a container elements and a set of item elements. Flickity has a similar concept with its gallery element and set of cell elements. Draggabilly works with mouse/touch events to make elements draggable. Flickity uses much of the same code to make the gallery draggable.

See the Pen Flickity begins 1 - slider by David DeSandro (@desandro) on CodePen.

From there, the remaining physics logic was copy/pasted over and converted for the library.

See the Pen Flickity begins 2 - selected cell physics by David DeSandro (@desandro) on CodePen.


Writing code comes in two phases. First is expansion: writing whatever code will get the job done; bringing ideas to life on the screen. Second is refactoring: going back to revise and edit after a concept has been realized. It's useful to be distinct about these two phases and not try to do both at the same time. Ideas and creativity should run uninhibited. Likewise, refactoring works better when an idea has been fully executed. Write drunk, edit sober. Something like that.


This initial version is fairly naïve, but it has the basic feature set that will be the foundation of the super-library to come.

  • Create slider element
  • Get cell elements
  • Position cell elements
  • Listen to touch/mouse events
  • Position slider with dragging and post-drag physics

Everything from here on out will be small details: adding optional features, additional UI, and resolving edge cases.


Flickity is now up on GitHub. It's nowhere near ready for proper use, but you can follow along development as a consolation.

As I write more about the development process, I'm realizing that I could be skipping over some big steps. (For example, there's a big portion of the code that deals with this Unipointer library.) If there's something you're curious about, please ping me @metafizzyco. I'm happy to dive deeper :)

Previously

If you're just joining us, I'm making a new gallery library! The story thus far...