I made browser buddy for my own purposes. I found myself working on a lot of small sites that had similar needs for lightweight scroll effects, animations, and dom manipulations.
Download it from here. Maybe it will be on npm or bower some day or something, but it isn't now.
Browser Buddy has several independent modules that work just fine on their own, but can be composed together to perform common functions. Currently there are four modules:
bb.cache
bb.animator
bb.scroll
bb.dom
bb.cache
is a tool for storing and retrieving values that can be expensive to calculate, like element measurements, computed window
properties, etc. It has two caching tiers for volatile and stable values. You can store things like element heights in the stable cache, and things like scroll position in the volatile cache.
bb.animator
is an interface for an animation tool that uses window.requestAnimationFrame
under the hood. You can use it to actually animate stuff, or use it to execute code that needs to respond to constantly changing conditions.
bb.scroll
provides an evented interface to react to scroll behaviors.
bb.dom
is a kind of bad interface for creating dom elements. Hopefully it will get better soon. If you have a lot of dom editing to do, probably definitely don't use this.
Stores a volatile cache entry. The entry must be a property (prop
) of an object (obj
) that will continue to be accessible. accessKey
is whatever name you want to access it by. When you want to retrieve the stored value from the cache, just get it by referencing bb.cache.accessKey
, where accessKey
is the value you stored it with.
Stores a stable cache entry. Same deal as storeVolatile
but it lives in a different cache tier. Access works the same way though.
Intelligently stores and retrieves expensive element measurements. This is basically just a layer on top of element.getBoundingClientRect()
, so measurement
should be a string that matches a property returned by getBoundingClientRect()
. This method will cache the element's rect
and return either the cached measurements, or if it hasn't been cached yet, it will add it to the cache.
The cached rects
are treated as stable entries, so they are recalculated when bb.cache.refresh
is called.
Updates all volatile cache entries.
Updates all stable and volatile cache entries.
An alternative to just accessing a value directly via bb.cache.accessKey
. This can be helpful if you need to delay accessing a cached value until a function is actually run. Like with closures or whatever.
Starts the animation loop.
Stops the animation loop.
Advances the animation loop one frame
Sets the number of frames in the animator period (see bb.animator.enqueuePeriodical()
).
Provide a function to be run every frame.
Provide a function to be run once per period. Adjust the length of the animation period by changing bb.animator.period
.
This will animate a value from 0 to 1 over the duration and easing specified. You provide a callback function cb
that accepts a float as it's argument and does with it what it will.
For example, you might do this to hide a header element:
var el = document.querySelector('.header');
bb.animator.animate(2000, bb.animator.easing.easeInOutCubic, function(progress) {
var t = progress * -200;
el.style.transform = 'translate3d(0, ' + t + 'px, 0)';
});
If you want to provide your own easing function, you can. It should accept a value from 0–1 and return a value (usually from 0–1 but you do you).
A static set of easing functions. Available easings are:
- linear
- easeInQuad
- easeOutQuad
- easeInOutQuad
- easeInCubic
- easeOutCubic
- easeInOutCubic
- easeInQuart
- easeOutQuart
- easeInOutQuart
- easeInQuint
- easeOutQuint
- easeInOutQuint
Note: I did not make these, they came from here: https://gist.github.com/gre/1650294
Provide an element, and an options object. Currently the only options you can provide are topFocusBoundary
and bottomFocusBoundary
. I will explain those in a minute.
Once you have called bb.scroll.watch
, you can have the element you passed to it listen for different scrolling events and react to them! The events you can listen for are:
bb.scroll.enteredViewEventName
: Triggered when an element first enters the viewport.bb.scroll.inViewEventName
: Triggered when an element is in the viewport, provides adetail.progress
property on the event indicating how far across the viewport the element has traveled.bb.scroll.exitedViewEventName
: Triggered when an element leaves the viewport.bb.scroll.enteredFocusEventName
: Triggered when an element first enters the defined focus area of the viewport.bb.scroll.inFocusEventName
: Triggered when an element is in the focus area of the viewport, provides adetail.progress
property on the event indicating how far across the focus area the element has traveled.bb.scroll.exitedFocusEventName
: Triggered when an element leaves the focus area of the viewport.
You can define the focus area by providing topFocusBoundary
(distance from the top of the viewport to the top of the focus area) and bottomFocusBoundary
(distance from the top of the viewport to the bottom of the focus area) as opts
to the bb.scroll.watch
method.
If you wanted to show a tooltip for a new thing when that thing enters the middle third of the viewport, here is how you would do it.
var newEl = document.querySelector('.item.new');
bb.scroll.watch(newEl, {
topFocusBoundary: window.innerHeight / 3,
bottomFocusBoundary : 2 * (window.innerHeight / 3)
});
newEl.addEventListener(bb.scroll.enteredFocusEventName, function () {
this.querySelector('.tooltip').classList.add('visible');
});
newEl.addEventListener(bb.scroll.exitedFocusEventName, function () {
this.querySelector('.tooltip').classList.remove('visible');
});
In my experience, this is totally performant enough to do clean parallax scroll effects, however, one trick that will save you a lot of recalculations is to do a little nesting so that you can watch the scroll position of a non-parallaxing element and then translate an element inside of it.
E.g.:
var container = document.querySelector('.parallax-container');
var mover = container.querySelector('img');
container.style.height = bb.cache.getElementMeasurement(mover, 'height') + 'px';
bb.scroll.watch(container);
container.addEventListener(bb.scroll.inViewEventName, function (e) {
mover.style.transform = 'translate3d(0, ' + e.detail.progress * 200 + 'px, 0);';
});
Recalculates element positions. Uses bb.cache
to store them, so if you need to use this method, you may need to call bb.cache.refresh
before you do it.
Checks all watched elements' positions and fires appropriate events.
Not gonna bother documenting this right now because it's not very good. I'll try to make a better one later.
Browser Buddy comes with a default configuration that makes a few useful connections for you. To use the default config, call bb.initDefaultConfig()
.
- Plugs
bb.cache.update
into the animator's standardenqueue
method, so volatile entries are refreshed once per frame. - Plugs
bb.cache.refresh
into the animator'senqueuePeriodical
method, so stable entries are calculated once per animation period - Plugs
bb.scroll.tick
into the animator'senqueue
method, so thatbb.scroll
events are fired once per frame. - Plugs
bb.scroll.recalculate
into the animator'senqueuePeriodical
method, so that watched elements' measurements are updated once per animation period - Plugs
bb.cache.refresh
andbb.scroll.recalculate
into thewindow.onresize
method so that element measurements are recalculated when the window size changes.