Scrollmeister Documentation

I'm sorry, the docs are a hot mess and don't work at all. I promise it's on my list.

Introduction

What it is good for

Scrollmeister is an open-source JavaScript framework to declaratively build scrolling experiences. Using custom elements (<scroll-meister>, <element-meister> and <shadow-meister>) you can create complex interactive scrolling pages without a single line of code. All you need is an HTML editor and you're good to go, you can even render the pages on the server. Scrollmeister comes with it's own layout engine called Guides Layout. It was built from the ground up with scrolling interactions in mind. This makes it ridiculously performant (on both desktop and touch devices).

When not to use it

Scrollmeister solves a particular problem really well, but it is important to understand when not to use Scrollmeister.

Concepts

Guide Layout

Behaviors

attach and detach events. E.g. layout:detach

^guides-layout

The guides-layout behavior can be added to the <scroll-meister> element. It defines the available guides and the maximum content width.

guides

Default: none

Guides are vertical lines that elements snap to. They have a name, position and width. Learn more about Guides layout.

<scroll-meister guides-layout="guides: left 0 1vmin, center 0.5 0, right 0 1vmin;"></scroll-meister>

A special viewport guide keyword is always defined. It can be used in the layout behavior for both left and right guides. It will snap the element to the left or right edge of the viewport.

width

Default: 1280px

The width property specifies the maximum width for an imaginary wrapper for the guides to orient. It is always centered in the viewport like a container element. Once the viewport becomes too narrow, it get's fluid and has the full width. Guides never leave the viewport.

<scroll-meister guides-layout="width: 960px;"></scroll-meister>

^debug-guides ← ^guides-layout

Draws the guides on top of the page so you can see them. Doesn't accept any properties. Requires the guides-layout behavior.

<scroll-meister debug-guides></scroll-meister>

^fadein

Does nothing except for setting opacity to 0 as soon as it is attached. Doesn't accept any properties. This is useful to prevent the flash-of-unlayout.

<scroll-meister fadein style="opacity: 0; transition: opacity 0.5s ease-in-out;"></scroll-meister>

layout ← ^guides-layout

The core behavior to layout elements.

guides

Default: viewport viewport

The name of two guides (defined in guides-layout) that the element will snap to.

<!--This element spans the right half of the viewport, assuming a "center" guide has been defined-->
<element-meister layout="guides: center viewport;"></element-meister>

height

Default: auto

Values like 100px or 50vh work as you would expect them. Percentages such as 50% are relative to the width of the element. This allows you to define elements with a fixed aspect ratio. There is also syntactic sugar for ratios using something like 1920/1080 will be converted to percentages internally.

<!--This element will always have an aspect ratio of 16:9.-->
<element-meister layout="height: 16/9;"></element-meister>

spacing

mode

Default: flow

There are two layout modes flow and follow. The flow mode works as you'd expect by putting each element below the previous one (note: the order can be influenced using the dependencies property). The follow mode removes the element from the document flow and instead makes it follow its leaders (see followerMode and dependencies).

<!--This element flows regularly.-->
<element-meister layout="mode: flow;"></element-meister>

<!--This element follows the above element and does not affect the flow.-->
<element-meister layout="mode: follow;"></element-meister>

<!--This element flows regularly behind the very first one, ignoring the follower.-->
<element-meister layout="mode: flow;"></element-meister>

followerMode

Default: parallax

A follower can follow its leader in either a parallax or a pin fashion.

<!--This element follows its leader in a parallax motion.-->
<element-meister layout="mode: follow; followerMode: parallax;"></element-meister>
<!--This element follows its leader and is pinned while the leader is inside the viewport.-->
<element-meister layout="mode: follow; followerMode: pin;"></element-meister>

pinAnchor

Default: center

pinOffset

Default: 0

clip

dependencies

interpolate ← layout

transform ← interpolate

gl-effect

media

scrub ← interpolate

scratch ← interpolate

fullscreenable ← layout

mousetrap ← ?fullscreenable

gallery

youtube

JavaScript API

Events

Scrollmeister.defineBehavior(BehaviorClass)

The defineBehavior function defines a new behavior. Behaviors need to be defined before the DOM is ready in a synchronous script right after the Scrollmeister core.

The following example defines an ExampleBehavior. By convention it also defines its behaviorName as example to match the class name.

Scrollmeister.defineBehavior(
  class ExampleBehavior extends Scrollmeister.Behavior {
    static get schema() {
      return {};
    }

    static get behaviorName() {
      return 'example';
    }

    static get dependencies() {
      return [];
    }

    attach() {}

    //These two methods are entirely optional.
    update(prevProps, prevState) {}
    detach() {}
  }
);

The behavior can now be used by adding an example attribute like this <element-meister example></element-meister>. Check out the behavior tutorial to learn more about creating behaviors.

schema()

The static schema getter defines the properties that the behavior expects. For example the following defines a size property of type csslength with a default of 100px.

class ExampleBehavior extends Scrollmeister.Behavior {
  static get schema() {
    return {
      size: {
        type: 'csslength',
        default: '200px'
      }
    };
  }

  static get behaviorName() {
    return 'example';
  }

  static get dependencies() {
    return [];
  }

  attach() {}
  update(prevProps, prevState) {}
  detach() {}
}

Now you could use it like this <element-meister example="size: 30%;"></element-meister> and you can now magically access this.props.size ({length: 30, unit: '%'}) inside the behavior.

The following types are currently supported

type can not only be a string like 'csslength' but also an array or an array of arrays to denote a list of values. Here are all four possibilities:

1. single value

{
  name: {
    type: 'string';
  }
}
<element-meister example="name: alex;"></element-meister>
console.log(this.props.name);
//'alex'

2. list of values

{
  friends: {
    type: ['string'];
  }
}
<element-meister example="friends: sarah, philipp, tim;"></element-meister>
console.log(this.props.friends);
//['sarah', 'philipp', 'tim']

3. named values

{
  guide: {
    type: [{ name: 'string' }, { position: 'csslength' }];
  }
}
<element-meister example="guide: center 400px;"></element-meister>
console.log(this.props.guide);
//{name: 'center', position: {length: 400, unit: 'px'}}

4. list of named values

{
  guides: {
    type: [[{ name: 'string' }, { position: 'csslength' }]];
  }
}
<element-meister example="guides: center 400px, right 100%;"></element-meister>
console.log(this.props.guides);
//[
//  {name: 'center', position: {length: 400, unit: 'px'}},
//  {name: 'right', position: {length: 100, unit: '%'}}
//]

behaviorName

The static behaviorName getter returns the name of the attribute that represents this behavior. Adding an attribute with this name to an element will attach the behavior, removing the attribute will detach it. The value of the attribute are the props of the behavior as defined in the schema. They are automatically parsed and provided to the behavior.

dependencies

An array of names of behaviors that this behavior depends on. Scrollmeister will guarantee that behaviors are attached in the correct order and will complain if a dependency is missing. The other behaviors can be access from within a behavior via the element. E.g. this.el.layout can access the layout behavior.

Behavior API

Since every behavior inherits from Scrollmeister.Behavior they share a common API.

el / parentEl

this.el inside a behavior is a reference to the DOM element it is attached to. Additionally in case of <element-meister> elements, this.parentEl is a reference to the <scroll-meister> parent.

props

The schema you've defined is used internally to parse the props (the value of the attribute). You can access the parsed props using this.props.

<element-meister example="size: 30%;"></element-meister>
class ExampleBehavior extends Scrollmeister.Behavior {
  static get schema() {
    return {
      size: {
        type: 'csslength'
      }
    };
  }

  static get behaviorName() {
    return 'example';
  }

  static get dependencies() {
    return [];
  }

  attach() {
    //The parsed value, e.g. {length: 30, unit: '%'}
    console.log(this.props.size);
  }

  update(prevProps, prevState) {}
  detach() {}
}

To update all properties you can use setAttribute, but this is discouraged and not really useful to update individual properties. Instead you can get and set the raw value for a prop directly on the behavior instance.

<element-meister example="size: 30%;"></element-meister>
//This will internally parse the value and update this.props.size.
document.querySelector('element-meister').example.size = '50%';

listen / listenOnce / listenAndInvoke / unlisten

These methods can be used to attach and remove event listeners to DOM nodes. They all share the same signature (element, eventName, callback). The first parameter is optional and defaults to the element that the behavior is currently attached to. What makes all three variants of listen* so handy (in comparison to just manually using addEventListener) is that you don't need to clean up. The behavior will automatically remove all event listeners when it is detached.

listen(element, eventName, callback)

class ExampleBehavior extends Scrollmeister.Behavior {
  static get schema() {
    return {};
  }

  static get behaviorName() {
    return 'example';
  }

  static get dependencies() {
    return [];
  }

  attach() {
    //It's safe to use an anonymous arrow function
    //because the behavior will clean up automatically.
    this.listen('click', () => {
      alert('clicked');
    });

    this.listen(window, 'load', () => {
      alert('load');
    });
  }

  update(prevProps, prevState) {}
  detach() {}
}

listenOnce(element, eventName, callback)

Just like listen but automatically unbound after the first event.

listenAndInvoke(element, eventName, callback)

Just like listen but the callback is immediately invoked once synchronously. This is handy when you want to react to an event but also want to make sure to set everything up initially.

class ExampleBehavior extends Scrollmeister.Behavior {
  static get schema() {
    return {};
  }

  static get behaviorName() {
    return 'example';
  }

  static get dependencies() {
    return [];
  }

  attach() {
    this.listenAndInvoke(window, 'resize', () => {
      //Query the window size and do stuff here.
    });
  }

  update(prevProps, prevState) {}
  detach() {}
}

unlisten(element, eventName, callback)

Does what you'd expect and unbinds the listener. Note that you rarely need to use unbind manually. Also remember that when using .bind() a new function is returned. You need to pass a reference to the same function you've originally passed to listen*

emit(name, bubbles = true)

Will emit an event on the DOM element the behavior is attached to. The event name will automatically be namespaced with the name of the behavior. So if you emit a cuddle event on the dog behavior an event named dog:cuddle will be dispatched. A reference to the behavior will be available as event.details to all listeners.

document.addEventListener('example:lasers', e => {
  alert('Example lasers, the worst of all lasers!!!');
  console.log(e.details instanceof ExampleBehavior);
});
class ExampleBehavior extends Scrollmeister.Behavior {
  static get schema() {
    return {};
  }

  static get behaviorName() {
    return 'example';
  }

  static get dependencies() {
    return [];
  }

  attach() {
    this.emit('lasers');
  }

  update(prevProps, prevState) {}
  detach() {}
}

Scrollmeister.defineCondition