Odo Helpers
All the little things.
Support
IE9+ with polyfill.
To support IE<=11, you will need to add classList
polyfill.
Dependencies
Odo Device, Object.assign
, String.prototype.includes
Animation Helpers
onTransitionEnd and cancelTransitionEnd
Setup
When the button is clicked, this example cancels the last transition end listener. It then updates the text and waits for a new transition end callback.
var transitionEndCount = 0;
var transitionId = null;
var $block = $('#animate-my-left');
var $output = $('#left-output');
$('#left-animator').on('click', function() {
OdoHelpers.cancelTransitionEnd(transitionId);
$output.text('waiting for transition: ' + transitionEndCount);
$block.toggleClass('active');
transitionId = OdoHelpers.onTransitionEnd($block, function() {
$output.text('transition ended: ' + transitionEndCount++);
});
});
Method Signature
This method can take an optional context to call
the callback function with, a specific property to wait for the transition event on, and an optional timeout (in milliseconds) before the transition is considered complete and the callback will be invoked.
elem
can be either a regular Element
or jQuery
instance.
var transitionId = OdoHelpers.onTransitionEnd(elem, fn, context, optProperty, optTimeout);
OdoHelpers.cancelTransitionEnd(transitionId);
onAnimationEnd(elem, fn, context)
For keyframe animations that do not infinitely loop, you can get a callback when it finishes. In this example, the spinning
class is added to the element which adds an animation-name
property.
Side note: this is how you can restart a keyframe OdoHelpers.
Styles (prefixes omitted)
.my-keyframes {
animation-duration: 500ms;
animation-timing-function: ease-out;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
.my-keyframes.spinning {
animation-name: spin;
}
@keyframes spin {
50% {
border-radius: 50%;
background-color: #27ae60;
}
to {
transform: rotate(360deg);
}
}
$('#spin-it').on('click', function() {
var $block = $('.my-keyframes');
$block.addClass('spinning spun');
OdoHelpers.onAnimationEnd($block, function() {
$block.removeClass('spinning');
}, this);
});
Fading elements
fadeOutElement(elem, optFn, optThis, optInvisible)
fadeInElement(elem, optFn, optThis, optInvisible)
Fade an element in or out using classes. elem
is the element to fade. You can also provide a callback for the second parameter, a context, and whether or not to add visibility:hidden|visible when the transition finishes.
Warning! This requires you to have 3 css classes. fade
, in
, and invisible
.
OdoHelpers.fadeOutElement(document.getElementById('darth-fader'));
Using all the parameters:
OdoHelpers.fadeOutElement(document.getElementById('darth-fader'), function() {
console.log('Faded out.', this.hello);
}, { hello: 'world' }, true);
Remove the transition listener
The fade methods use onTransitionEnd
internally and return the the same value onTransitionEnd
does.
var fadeId = OdoHelpers.fadeInElement(document.getElementById('darth-fader'));
OdoHelpers.cancelTransitionEnd(fadeId);
Required CSS
.fade {
transition: opacity 150ms ease-out;
opacity: 0;
}
.fade.in {
opacity: 1;
}
.invisible {
visibility: hidden;
}
scrollTo(position, speed, callback, easing) and scrollToTop()
With scrollTo
and scrollToTop
, you can animate the scroll top of the window.
$('#started-from-the-bottom').on('click', function() {
var bottom = $(document).height() - $(window).height();
var duration = 600;
var easing = function (k) {
return -k * (k - 2);
};
OdoHelpers.scrollTo(bottom, duration, function() {
console.log('done!');
}, easing);
});
$('#take-me-to-the-top').on('click', function() {
OdoHelpers.scrollToTop();
});
Animation Stepper
A requestAnimationFrame
throttled replacement for jQuery's animate. It is a simple class for providing a stepping function for each animation frame over a given length of time. Its API is similar to the AnimationPlayer
in Element.animate.
var stepper;
$('#step-it').on('click', function() {
// Stop any previous steppers from finishing.
if (stepper) {
stepper.cancel();
}
stepper = new OdoHelpers.Stepper({
start: 0,
end: 180,
duration: 800,
context: { taco: 'Tuesday' },
step: function(value, percent) {
// this === { taco: 'Tuesday' }
// Position block
…
// Update display.
$stepOutput.html(value.toFixed(0) + '/180<br>' + Math.round(percent * 100) + '%')
},
// Custom easing
easing: function(k) {
return -(--k * k * k * k - 1);
}
});
stepper.onfinish = function() {
$stepOutput.html($stepOutput.html() + '<br>Done!');
};
});
More easings
- bezier-easing project which can convert a CSS easing to a JavaScript function.
- An easing gist
- jQuery easings
Array Helpers
closest(arr, num)
Given an array of numbers, find the item in the array closest to a given number.
OdoHelpers.closest([0, 100, 200, 300, 400, 500], 180) // 200
closestLessThan(arr, num)
Given an array of numbers, find the item in the array closest to a given number while also less than the given number.
OdoHelpers.closestLessThan([0, 100, 200, 300, 400, 500], 180) // 100
closestGreaterThan(arr, num)
Given an array of numbers, find the item in the array closest to a given number while also greater than the given number.
OdoHelpers.closestGreaterThan([0, 100, 200, 300, 400, 500], 200) // 300
chunk(array, size)
Make an array of smaller arrays from an array.
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
OdoHelpers.chunk(arr, 4) // [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
OdoHelpers.chunk(arr, 5) // [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
pull(array, item)
Remove an item from an array.
var letters = ['a', 'b', 'c', 'd'];
OdoHelpers.pull(letters, 'c');
console.log(letters); // ['a', 'b', 'd'];
Browser Helpers
Setting Hash
Sets the window location hash. This will create an entry in the user's history.
$('#hash-click').on('click', function() {
OdoHelpers.setHash('#awwyiss');
})
Setting History State
Sets the URL's hash like setHash
, but this method does not create an entry in the user's history (unless the browser doesn't support replaceState
(IE9)).
$('#replace-hash-click').on('click', function() {
OdoHelpers.replaceWithHash('#awwyissssss');
})
User agent sniffing 🙃
Because sometimes you can't feature-test things.
OdoHelpers.isAndroidOS(navigator.userAgent);
OdoHelpers.isIOS(navigator.userAgent);
OdoHelpers.isChrome(navigator.userAgent);
OdoHelpers.isIE(navigator.userAgent);
OdoHelpers.isEdge(navigator.userAgent);
OdoHelpers.getIOSVersion(navigator.userAgent);
OdoHelpers.hasScrollEvents(navigator.userAgent);
OdoHelpers.isNativeAndroid(navigator.userAgent);
isAndroidOS | |
isIOS | |
hasScrollEvents | |
getIOSVersion | |
isChrome | |
isNativeAndroid | |
isIE | |
isEdge |
Coordinate
Class for representing coordinates and positions.
Coordinate(optX, optY)
If the x
or y
value is not specified, it will be zero.
var Coordinate = OdoHelpers.Coordinate;
var start = new Coordinate(10, 50);
console.log(start.x); // 10
console.log(start.y); // 50
distance(a, b)
Calculate the distance between two coordinates.
var end = new Coordinate(50, 100);
Coordinate.distance(start, end); // -64.03
equals(a, b)
Determine whether two coordinates are the same.
var p1 = new Coordinate(10, 10);
var p2 = new Coordinate(10, 10);
var p3 = new Coordinate(21, 10);
Coordinate.equals(p1, p1); // true
Coordinate.equals(p1, p3); // false
sum(a, b)
Calculate the sum between two coordinates as a new Coordinate.
Coordinate.sum(start, end); // {x: 60, y: 150 }
difference(a, b)
Calculate the difference between two coordinates as a new Coordinate.
Coordinate.difference(start, end); // {x: -40, y: -50 }
product(a, b)
Calculate the product between two coordinates as a new Coordinate.
Coordinate.product(start, end); // {x: 500, y: 5000 }
quotient(a, b)
Calculate the quotient between two coordinates as a new Coordinate.
Coordinate.quotient(start, end); // {x: 0.2, y: 0.5 }
scale(a, sx, optSy)
Returns a scaled clone of a
without modifying a
.
Coordinate.scale(start, 0.5); // {x: 5, y: 25 }
clone()
Returns a new coordinate with this coordinates points.
var position = new Coordinate(100, 100);
var p2 = position.clone();
console.log(p2); // { x: 100, y: 100 }
scale(sx, optSy)
Scales this coordinate by the given scale factors.
var position = new Coordinate(100, 100);
position.scale(0.6);
console.log(position); // { x: 60, y: 60 }
translate(tx, ty)
Translates this box by the given offsets. If a Coordinate
is given, then the x and y values are translated by the coordinate's x and y. Otherwise, x and y are translated by tx
and opt_ty
respectively.
var position = new Coordinate(100, 100);
position.translate(5, 10);
console.log(position); // { x: 105, y: 110 }
position.translate(new Coordinate(-5, -10));
console.log(position); // { x: 100, y: 100 }
DOM Helpers
There are more than what’s listed here. The unlisted ones are self-explanatory.
getRelativeDepth(node, parentNode)
Returns the depth of the given node relative to the given parent node, or -1 if the given node is not a descendant of the given parent node. E.g. if node == parentNode
it returns 0
, if node.parentNode == parentNode
it returns 1
, etc.
<grandpa>
<mom>
I'm a text node.
<bob></bob>
</mom>
</grandpa>
var bob = document.querySelector('bob');
var grandpa = document.querySelector('grandpa');
OdoHelpers.getRelativeDepth(bob, grandpa); // 1
getNthSibling(node, n, optIsForward)
Retrieves the nth sibling of an element, or null if the would be nth sibling does not exist. Heads up! This function excludes text nodes.
<carousel>
<slide data-start></slide>
<slide></slide>
<slide></slide>
<slide data-find-me></slide>
</carousel>
var start = document.querySelector('slide[data-start]');
OdoHelpers.getNthSibling(start, 3); // <slide data-find-me></slide>
OdoHelpers.getNthSibling(start, 4); // null
DOM Ready Promise
Returns a promise that resolves once the document ready state is 'interactive'. If the DOMContentLoaded
event has not been dispatched yet, the promise will resolve after this event is fired.
OdoHelpers.domReady resolved
OdoHelpers.domReady.then(function() {
var end = new Date().getTime() - date;
$('#dom-ready-text').text(end + ' ms');
$('#dom-ready').addClass('block--fired');
});
DOM Loaded Promise
Returns a promise that resolves once the document ready state is 'complete'. If the load
event has not been dispatched yet, the promise will resolve after this event is fired.
OdoHelpers.domLoaded resolved
OdoHelpers.domLoaded.then(function() {
var end = new Date().getTime() - date;
$('#dom-loaded-text').text(end + ' ms');
$('#dom-loaded').addClass('block--fired');
});
Math Helpers
Math utilities
clamp(value, min, max)
Takes a number and clamps it to within the provided bounds.
OdoHelpers.clamp(100, 0, 10) // 10
OdoHelpers.clamp(5, 0, 10) // 5
OdoHelpers.clamp(-1, 0, 10) // 0
wrapAroundList(index, displacement, length)
Calculates the offset index for a circular list. For example, a looping carousel. When you're at the last slide and increment the slide to wish to go to, it should go to the first one.
var currentIndex = 3;
var listLength = 4;
OdoHelpers.wrapAroundList(currentIndex, 1, listLength); // 0
OdoHelpers.wrapAroundList(currentIndex, 2, listLength); // 1
OdoHelpers.wrapAroundList(currentIndex, 3, listLength); // 2
OdoHelpers.wrapAroundList(currentIndex, 4, listLength); // 3
OdoHelpers.wrapAroundList(currentIndex, 5, listLength); // 0
Box
Class for representing a box. A box is specified as a top, right, bottom, and left. A box is useful for representing margins and padding.
var marginBox = new OdoHelpers.Box(top, right, bottom, left);
Rect
Class for representing rectangular regions.
var boundary = new OdoHelpers.Rect(left, top, width, height);
String Helpers
Get gooder with words #yolo.
capitalize(str)
Be proper!
OdoHelpers.capitalize('foo'); // 'Foo'
OdoHelpers.capitalize('foo bar'); // 'Foo bar'
hyphenate(str)
Hyphenates a javascript style string to a css one.
OdoHelpers.hyphenate('MozBoxSizing'); // '-moz-box-sizing.'
OdoHelpers.hyphenate('boxShadow'); // 'box-shadow'
randomString(str)
Creates a random string for IDs, etc.
OdoHelpers.randomString(); // 'qw7i22r19k9'
OdoHelpers.randomString(); // '75042h5b3xr'
Style Helpers
Size, Padding, and Margins
Get the height and width of an element when the display is not none with getSize
. If it or its parent is display none, this won't work properly.
<div id="get-me" style="margin:5px auto 1em 2%;padding:1em 2% 5px 0;background:tan;">I have margins and padding.</div>
var element = document.getElementById('get-me');
var output = document.getElementById('its-raining');
var echo = function(str) {
output.innerHTML = JSON.stringify(str, null, ' ');
};
$('#get-margins').on('click', function() {
echo(OdoHelpers.getMarginBox(element));
});
$('#get-paddings').on('click', function() {
echo(OdoHelpers.getPaddingBox(element));
});
$('#get-size').on('click', function() {
echo(OdoHelpers.getSize(element));
});
getElementsSize(elements, dimension)
Returns the size (width or height) of a list of elements, including margins. It takes an array (not a NodeList
) of elements and a dimension.
var wrapper = document.getElementById('get-elements-wrapper');
var output = document.getElementById('output-elements-size');
$('#get-elements-size').on('click', function() {
var width = OdoHelpers.getElementsSize(Array.from(wrapper.children), 'width');
output.textContent = width + ' pixels';
});
forceRedraw()
Force the page to be repainted in WebKit and Blink. This is often useful for native Android browsers because they do not update their state often enough, for example, after checking a checkbox.
In this example, you won't see anything change.
$('#force-redraw').on('click', OdoHelpers.forceRedraw);
causeLayout(element)
Ask the browser for a property that will cause it to recalculate styles and layout the element (and possibly surrounding/parent elements).
In this example, the block is resting at 1
opacity. We want it to fade up from 0
to 1
opacity. By asking for the offsetWidth
of the block (which is what causeLayout
does), it forces the browser to apply any operations in the previous batch. Without the layout, the browser will batch-process your code and it will only see the block is supposed to be 1
opacity and it's already at 1
opacity, so there won't be a transition.
#blocktacular.faded {
opacity: 0;
}
#blocktacular.active {
opacity: 1;
transition: 400ms;
}
var checkbox = document.getElementById('should-i-do-it');
var block = document.getElementById('blocktacular');
$('#lets-get-ready-to-rumble').on('click', function() {
$(block).addClass('faded');
if (checkbox.checked) {
OdoHelpers.causeLayout(block);
}
$(block).addClass('active');
setTimeout(function() {
$(block).removeClass('faded active');
}, 500);
});
evenHeights(groups)
Can we base-align the descriptions?
Use evenHeights
to find the tallest element then set all the elements to that height.
SFO - Odo Hotspots
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Odo Scroll Animation
Ut enim ad minim veniam.
SFO - Odo The Longest Component of All Time!
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Odo Carousel
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
$('#even-the-heights').on('click', function() {
var tallest = OdoHelpers.evenHeights($('#first-row .product__title').get());
console.log(tallest); // 34 ish
});
Aww, now the column backgrounds are still not aligned.
Multiple groups of elements can also be set. In this example, the second rows' .product
and .product__title
are set to the same height. This is much more performant than calling evenHeights
twice.
Odo Carousel
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
SFO - Odo The Longest Component of All Time!
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Odo Scroll Animation
Ut enim ad minim veniam.
SFO - Odo Hotspots
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
$('#even-them-all').on('click', function() {
var groups = [
$('#second-row .product__title').get(),
$('#second-row .product').get()
];
var tallestArray = OdoHelpers.evenHeights(groups);
console.log(tallestArray); // [34, 140] ish
});
Timer Class
A simple timer class. The timer does not start automatically when initialized. OdoHelpers.Timer
continuous Option
If true
, the timer will automatically restart itself when it expires.
var continuous = false;
var duration = 400;
var timer = new Timer(function() {
$('#timer-block').css('backgroundColor', 'hsl(' + Math.round(Math.random() * 360) + ',58%,50%)');
}, duration, continuous);
General Utilities
defaultsTo(value, defaultValue, test)
Fall back to a specified default if an input is undefined or null. Optionally provide your own test for the value to pass.
OdoHelpers.defaultsTo('foo', 'bar') // 'foo'
OdoHelpers.defaultsTo(5, {}) // 5
OdoHelpers.defaultsTo(null, 'bar') // 'bar'
var fn = function() {};
OdoHelpers.defaultsTo(fn, 'bar', typeof fn === 'function') // fn
OdoHelpers.defaultsTo(fn, 'bar', typeof fn === 'number') // 'bar'
isString(value)
OdoHelpers.isString(5); // false
OdoHelpers.isString('JavaScript'); // true
isDefined(value)
OdoHelpers.isDefined(5); // true
OdoHelpers.isDefined(null); // false
OdoHelpers.isDefined(undefined); // false
getNumberOption(value, defaultValue)
Parse a value as a number. If it's not numeric, then the default value will be returned.
OdoHelpers.getNumberOption(5.2, 'foo'); // 5.2
OdoHelpers.getNumberOption('5.2', 'foo'); // 5.2
OdoHelpers.getNumberOption({}, 10); // 10
getStringOption(value, defaultValue)
Parse a value as a string. If it's not a string, then the default value will be returned.
OdoHelpers.getStringOption(5.2, 'foo'); // 'foo'
OdoHelpers.getStringOption('bar', 'foo'); // 'bar'
getPercentageOption(value, defaultValue)
Parse a value as a percentage. If it's a string with '%' in it, it will be parsed as a string, otherwise it will be parsed as a number.
OdoHelpers.getPercentageOption('50%', '100%'); // '50%'
OdoHelpers.getPercentageOption(50, '100%'); // 50
OdoHelpers.getPercentageOption(null, '100%'); // '100%'
noop()
A function which does nothing.
OdoHelpers.noop();
Events
Need a cross-browser transitionend
event name or the correct pointerdown
string?
console.log('transition end event name: ' + OdoHelpers.events.TRANSITIONEND);
console.log('animation end event name: ' + OdoHelpers.events.ANIMATIONEND);
console.log('pointer move event name: ' + OdoHelpers.events.POINTERMOVE);