Skip to content

Instantly share code, notes, and snippets.

@widged
Forked from grahamjenson/README.md
Last active August 29, 2015 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save widged/1b9ad1681a296b064dfd to your computer and use it in GitHub Desktop.
Save widged/1b9ad1681a296b064dfd to your computer and use it in GitHub Desktop.

Fork to demonstrate how promises can easily be replaced with some async functions. Added the option to switch between an animation where all tiles are moved in paralles and one where all tiles are moved in sequence.

A word of warning. I code javascript in the context of kiosk apps that run a specific browser. I am not used to have to support multiple browsers. I go for pure vanillajs whenever possible. Tested on the latest version of Chrome Canary. Should work on most modern browsers.


Original text follows

I recently wanted to implement a visualisation of a stream of incoming events. For the visualisation I wanted a list where the latest event is added to the beginning and the other events move down the list in a snake like pattern (to minimise the amount of movement). This is an example click on the squares to see my implementation in action. I used jQuery promises (which I recently blogged about here), metro css and coffeescript to implement this visualisation.

<html>
<meta charset="utf-8">
<body>
<link rel="stylesheet" href="https://rawgithub.com/olton/Metro-UI-CSS/master/css/metro-bootstrap.css">
<style>
body {
margin: 3px;
}
/* tile width 130 260 390 520 650 780 910 1040*/
@media screen and (min-width: 260) {
#list {
width: 260px;
}
#list :nth-child(4n+1), #list :nth-child(4n+2){
float: left;
}
#list :nth-child(4n+3), #list :nth-child(4n+4) {
float: right;
}
}
@media screen and (min-width: 390px) {
#list {
width: 390px;
}
#list :nth-child(6n+1), #list :nth-child(6n+2), #list :nth-child(6n+3){
float: left;
}
#list :nth-child(6n+4), #list :nth-child(6n+5), #list :nth-child(6n+6) {
float: right;
}
}
@media screen and (min-width: 520px) {
#list {
width: 520px;
}
#list :nth-child(8n+1), #list :nth-child(8n+2), #list :nth-child(8n+3), #list :nth-child(8n+4){
float: left;
}
#list :nth-child(8n+5), #list :nth-child(8n+6), #list :nth-child(8n+7), #list :nth-child(8n+8) {
float: right;
}
}
@media screen and (min-width: 650px) {
#list {
width: 650px;
}
#list :nth-child(10n+1), #list :nth-child(10n+2), #list :nth-child(10n+3), #list :nth-child(10n+4), #list :nth-child(10n+5){
float: left;
}
#list :nth-child(10n+6), #list :nth-child(10n+7), #list :nth-child(10n+8), #list :nth-child(10n+9), #list :nth-child(10n+10) {
float: right;
}
}
ul[layout='horizontal'] {
display: inline-block;
padding: 0;
margin: 0 ;
list-style: none;
}
ul[layout='horizontal'] > li {
display: inline-block;
margin: 0 6px 0 0;
}
ul.animation-type {
margin: 0px 0px 12px 0px;
}
.animation-type li {
color: white;
padding: 3px 18px;
background-color: darkgray;
}
.animation-type li.active {
background-color: black;
}
</style>
<script src='http://code.jquery.com/jquery-2.0.3.js'></script>
<script src='http://code.jquery.com/ui/1.10.3/jquery-ui.js'></script>
<div><ul class="animation-type" layout="horizontal"><li class="active" value="parallel">Parallel</li><li value="sequence">Sequence</li></ul></div>
<div class='metro' id='list'>
</div>
<script type="text/javascript" src='script.js'></script>
</body>
</html>
"use strict";
// ########################
// # Reusable Modules
// ########################
/**
* Minimalistic Template
*/
var Template = (function() {
var FN = {};
FN.multiline = function(fn) {
return fn.toString().match(/[\s\S]*\/\*([\s\S]*)\*\/[\s\S]*/)[1];
};
FN.replace = function(html, data) {
(Object.keys(data) || []).forEach(function(key) {
html = html.replace(new RegExp("{{" + key + "}}", "gi"), data[key]);
});
return html;
};
return FN;
}());
/**
* Minimalistic support for asynchronous processing of a list of items.
*/
var AsyncItems = (function() {
var FN = {};
FN.parallel = function(items, asyncItem, whenAllDone) {
var pending = items.length;
items.forEach(function(item) {
asyncItem(item, whenItemComplete);
});
function whenItemComplete() {
if(--pending === 0) { whenAllDone(); }
}
};
FN.sequence = function(items, asyncItem, whenAllDone) {
var current = 0;
function nextItem() {
asyncItem(items[current++], whenItemComplete);
}
function whenItemComplete() {
if(current >= items.length) {
whenAllDone();
} else {
nextItem();
}
}
whenItemComplete();
};
return FN;
}());
/**
* Various Utils function
*/
var Utils = (function() {
var FN = {};
FN.nodeList = function(args) { return (args) ? Array.prototype.slice.call(args) : []; };
FN.pickRandom = function(items) { return items[Math.floor(Math.random() * items.length)]; };
return FN;
}());
// ### Task specific code
var COLORS = ["bg-black", "bg-lime", "bg-green", "bg-emerald", "bg-teal", "bg-cyan", "bg-cobalt", "bg-indigo", "bg-violet", "bg-pink", "bg-magenta", "bg-crimson", "bg-red", "bg-orange", "bg-amber", "bg-yellow", "bg-brown", "bg-olive", "bg-steel", "bg-mauve", "bg-taupe", "bg-gray", "bg-dark", "bg-darker", "bg-darkBrown", "bg-darkCrimson", "bg-darkMagenta", "bg-darkIndigo", "bg-darkCyan", "bg-darkCobalt", "bg-darkTeal", "bg-darkEmerald", "bg-darkGreen", "bg-darkOrange", "bg-darkRed", "bg-darkPink", "bg-darkViolet", "bg-darkBlue", "bg-lightBlue", "bg-lightTeal", "bg-lightOlive", "bg-lightOrange", "bg-lightPink", "bg-lightRed", "bg-lightGreen"];
var ICONS = ["icon-home", "icon-newspaper", "icon-pencil", "icon-droplet", "icon-pictures", "icon-camera", "icon-music", "icon-film", "icon-camera-2", "icon-spades", "icon-clubs", "icon-diamonds", "icon-broadcast", "icon-mic", "icon-book", "icon-file", "icon-new", "icon-copy", "icon-folder", "icon-folder-2", "icon-tag", "icon-cart", "icon-basket", "icon-calculate", "icon-support", "icon-phone", "icon-mail", "icon-location", "icon-compass", "icon-history", "icon-clock", "icon-bell", "icon-calendar", "icon-printer", "icon-mouse", "icon-screen", "icon-laptop", "icon-mobile", "icon-cabinet", "icon-drawer", "icon-drawer-2", "icon-box", "icon-box-add", "icon-box-remove", "icon-download", "icon-upload", "icon-database", "icon-flip", "icon-flip-2", "icon-undo", "icon-redo", "icon-forward", "icon-reply", "icon-reply-2", "icon-comments", "icon-comments-2", "icon-comments-3", "icon-comments-4", "icon-comments-5", "icon-user", "icon-user-2", "icon-user-3", "icon-busy", "icon-loading", "icon-loading-2", "icon-search", "icon-zoom-in", "icon-zoom-out", "icon-key", "icon-key-2", "icon-locked", "icon-unlocked", "icon-wrench", "icon-equalizer", "icon-cog", "icon-pie", "icon-bars", "icon-stats-up", "icon-gift", "icon-trophy", "icon-diamond", "icon-coffee", "icon-rocket", "icon-meter-slow", "icon-meter-medium", "icon-meter-fast", "icon-dashboard", "icon-fire", "icon-lab", "icon-remove", "icon-briefcase", "icon-briefcase-2", "icon-cars", "icon-bus", "icon-cube", "icon-cube-2", "icon-puzzle", "icon-glasses", "icon-glasses-2", "icon-accessibility", "icon-accessibility-2", "icon-target", "icon-target-2", "icon-lightning", "icon-power", "icon-power-2", "icon-clipboard", "icon-clipboard-2", "icon-playlist", "icon-grid-view", "icon-tree-view", "icon-cloud", "icon-cloud-2", "icon-download-2", "icon-upload-2", "icon-upload-3", "icon-link", "icon-link-2", "icon-flag", "icon-flag-2", "icon-attachment", "icon-eye", "icon-bookmark", "icon-bookmark-2", "icon-star", "icon-star-2", "icon-star-3", "icon-heart", "icon-heart-2", "icon-thumbs-up", "icon-thumbs-down", "icon-plus", "icon-minus", "icon-help", "icon-help-2", "icon-blocked", "icon-cancel", "icon-cancel-2", "icon-checkmark", "icon-minus-2", "icon-plus-2", "icon-enter", "icon-exit", "icon-loop", "icon-arrow-up-left", "icon-arrow-up", "icon-arrow-up-right", "icon-arrow-right", "icon-arrow-down-right", "icon-arrow-down", "icon-arrow-down-left", "icon-arrow-left", "icon-arrow-up-2", "icon-arrow-right-2", "icon-arrow-down-2", "icon-arrow-left-2", "icon-arrow-up-3", "icon-arrow-right-3", "icon-arrow-down-3", "icon-arrow-left-3", "icon-menu", "icon-enter-2", "icon-backspace", "icon-backspace-2", "icon-tab", "icon-tab-2", "icon-checkbox", "icon-checkbox-unchecked", "icon-checkbox-partial", "icon-radio-checked", "icon-radio-unchecked", "icon-font", "icon-paragraph-left", "icon-paragraph-center", "icon-paragraph-right", "icon-paragraph-justify", "icon-left-to-right", "icon-right-to-left", "icon-share", "icon-new-tab", "icon-new-tab-2", "icon-embed", "icon-code", "icon-bluetooth", "icon-share-2", "icon-share-3", "icon-mail-2", "icon-google", "icon-google-plus", "icon-google-drive", "icon-facebook", "icon-instagram", "icon-twitter", "icon-feed", "icon-youtube", "icon-vimeo", "icon-flickr", "icon-picassa", "icon-dribbble", "icon-deviantart", "icon-github", "icon-github-2", "icon-github-3", "icon-github-4", "icon-github-5", "icon-github-6", "icon-git", "icon-wordpress", "icon-joomla", "icon-blogger", "icon-tumblr", "icon-yahoo", "icon-amazon", "icon-tux", "icon-apple", "icon-finder", "icon-android", "icon-windows", "icon-soundcloud", "icon-skype", "icon-reddit", "icon-linkedin", "icon-lastfm", "icon-delicious", "icon-stumbleupon", "icon-pinterest", "icon-xing", "icon-flattr", "icon-foursquare", "icon-paypal", "icon-yelp", "icon-libreoffice", "icon-file-pdf", "icon-file-openoffice", "icon-file-word", "icon-file-excel", "icon-file-powerpoint", "icon-file-zip", "icon-file-xml", "icon-file-css", "icon-html5", "icon-html5-2", "icon-css3", "icon-chrome", "icon-firefox", "icon-IE", "icon-opera", "icon-safari", "icon-IcoMoon", "icon-sunrise", "icon-sun", "icon-moon", "icon-sun-2", "icon-windy", "icon-wind", "icon-snowflake", "icon-cloudy", "icon-cloud-3", "icon-weather", "icon-weather-2", "icon-weather-3", "icon-lines", "icon-cloud-4", "icon-lightning-2", "icon-lightning-3", "icon-rainy", "icon-rainy-2", "icon-windy-2", "icon-windy-3", "icon-snowy", "icon-snowy-2", "icon-snowy-3", "icon-weather-4", "icon-cloudy-2", "icon-cloud-5", "icon-lightning-4", "icon-sun-3", "icon-moon-2", "icon-cloudy-3", "icon-cloud-6", "icon-cloud-7", "icon-lightning-5", "icon-rainy-3", "icon-rainy-4", "icon-windy-4", "icon-windy-5", "icon-snowy-4", "icon-snowy-5", "icon-weather-5", "icon-cloudy-4", "icon-lightning-6", "icon-thermometer", "icon-compass-2", "icon-none", "icon-Celsius", "icon-Fahrenheit", "icon-forrst", "icon-headphones", "icon-bug", "icon-cart-2", "icon-earth", "icon-battery", "icon-list", "icon-grid", "icon-alarm", "icon-location-2", "icon-pointer", "icon-diary", "icon-eye-2", "icon-console", "icon-location-3", "icon-move", "icon-monitor", "icon-mobile-2", "icon-switch", "icon-star-4", "icon-address-book", "icon-shit", "icon-cone", "icon-credit-card", "icon-type", "icon-volume", "icon-volume-2", "icon-locked-2", "icon-warning", "icon-info", "icon-filter", "icon-bookmark-3", "icon-bookmark-4", "icon-stats", "icon-compass-3", "icon-keyboard", "icon-award-fill", "icon-award-stroke", "icon-beaker-alt", "icon-beaker", "icon-move-vertical", "icon-move-horizontal", "icon-steering-wheel", "icon-volume-3", "icon-volume-mute", "icon-play", "icon-pause", "icon-stop", "icon-eject", "icon-first", "icon-last", "icon-play-alt", "icon-battery-empty", "icon-battery-half", "icon-battery-full", "icon-battery-charging", "icon-left-quote", "icon-right-quote", "icon-left-quote-alt", "icon-right-quote-alt", "icon-smiley", "icon-umbrella", "icon-info-2", "icon-chart-alt", "icon-floppy", "icon-at", "icon-hash", "icon-pilcrow", "icon-fullscreen-alt", "icon-fullscreen-exit-alt", "icon-layers-alt", "icon-layers", "icon-rainbow", "icon-air", "icon-spin", "icon-auction", "icon-dollar", "icon-dollar-2", "icon-coins", "icon-file-2", "icon-file-3", "icon-file-4", "icon-files", "icon-phone-2", "icon-tablet", "icon-monitor-2", "icon-window", "icon-tv", "icon-camera-3", "icon-image", "icon-open", "icon-sale", "icon-direction", "icon-medal", "icon-medal-2", "icon-satellite", "icon-discout", "icon-barcode", "icon-ticket", "icon-shipping", "icon-globe", "icon-anchor", "icon-pop-out", "icon-pop-in", "icon-resize", "icon-battery-2", "icon-battery-3", "icon-battery-4", "icon-battery-5", "icon-tools", "icon-alarm-2", "icon-alarm-cancel", "icon-alarm-clock", "icon-chronometer", "icon-ruler", "icon-lamp", "icon-lamp-2", "icon-scissors", "icon-volume-4", "icon-volume-5", "icon-volume-6", "icon-zip", "icon-zip-2", "icon-play-2", "icon-pause-2", "icon-record", "icon-stop-2", "icon-next", "icon-previous", "icon-first-2", "icon-last-2", "icon-arrow-left-4", "icon-arrow-down-4", "icon-arrow-up-4", "icon-arrow-right-4", "icon-arrow-right-4", "icon-arrow-left-5", "icon-arrow-down-5", "icon-arrow-up-5", "icon-arrow-right-5", "icon-cc", "icon-cc-by", "icon-cc-nc", "icon-cc-nc-eu", "icon-cc-nc-jp", "icon-cc-sa", "icon-cc-nd", "icon-cc-pd", "icon-cc-zero", "icon-cc-share", "icon-cc-share-2", "icon-cycle", "icon-stop-3", "icon-stats-2", "icon-stats-3"];
/**
* Let's put everything that is about a tile within a separate module.
* Cleaner.
*/
var Tile = (function() {
// var Template = require('Template');
var animationDuration = 100;
var itemRenderer = Template.multiline(function() { /*
<div class="tile {{randomColor}}" uid="{{uid}}">
<div class='tile-content icon'>
<i class='{{randomIcon}}'></i>
</div>
<div class='brand bg-black'>
<span class='label fg-white'>{{uid}}</span>
</div>
</div>
*/});
var FN = {};
FN.render = function(uid) {
return Template.replace(
itemRenderer,
{
randomColor: Utils.pickRandom(COLORS),
randomIcon: Utils.pickRandom(ICONS),
uid: uid
}
);
};
FN.moveToNextSlot = function(node, asyncReturn) {
var $el = $(node);
if(!$el || !$el.length) { return asyncReturn(); }
var $to = $el.next();
if(!$to || !$to.length) { return asyncReturn(); }
$el.position({
my: 'center',
at: 'center',
of: $to,
using: (function(css) {
$el.animate(css, {
duration: animationDuration,
complete:asyncReturn
});
})
});
}
return FN;
}());
function main() {
// initial scene
var li = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1].map(Tile.render);
document.querySelector("#list").innerHTML = li.join("\n") + "<div class='tile' style='opacity: 0;'></div>";
// the tile list should animate anytime a tile is clicked
document.querySelector("#list").addEventListener("click", function() {
var tiles = Utils.nodeList(document.querySelectorAll("#list .tile"));
var parallelOrSequence = document.querySelector(".animation-type .active").getAttribute("value");
return AsyncItems[parallelOrSequence](tiles, Tile.moveToNextSlot, whenSnakeAnimationEnds);
});
// simplistic toggle component to select the animation type
document.querySelector(".animation-type").addEventListener("click", function(event) {
Utils.nodeList(document.querySelectorAll(".animation-type .active")).forEach(function(node) { node.classList.remove("active"); });
event.target.classList.add("active");
});
function whenSnakeAnimationEnds() {
var $tiles = $("#list .tile");
// must reset positions before a new tile is added.
$tiles.css("position", "").css("top", "").css("left", "");
// add a new tile and fade it in
var $tile = $(Tile.render($tiles.length)).prependTo("#list");
$tile.css("opacity", 0).animate({ opacity: 1 }, 500);
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment