Skip to content

Instantly share code, notes, and snippets.

@garris
Last active December 12, 2015 13:57
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save garris/2214de2100a4a67a2899 to your computer and use it in GitHub Desktop.
Save garris/2214de2100a4a67a2899 to your computer and use it in GitHub Desktop.
A Pen by Garris.
<html>
<head>
<title>TremulaJS</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="http://garris.github.io/TremulaJS/dist/Tremula.css">
<link rel="stylesheet" href="style.css">
<style type="text/css">
</style>
<script src="http://garris.github.io/TremulaJS/libs/jquery-min.js"></script>
<script src="http://garris.github.io/TremulaJS/libs/hammer.js"></script>
<script src="http://garris.github.io/TremulaJS/libs/jsBezier-0.6.js"></script>
<script src="http://garris.github.io/TremulaJS/dist/Tremula.js"></script>
<script src="script.js"></script>
</head>
<body class="doReflect">
<div class="tremulaContainer"></div>
<h1>TremulaJS</h1>
<span class="cta">Swipe, Scroll, Click & Drag</span>
</body>
</html>
$(document).ready(function(){
setTimeout(function(){
window.tremula = createTremula();
applyBoxClick();
loadFlickr()
},0);
});
function createTremula(){
// .tremulaContainer must exist and have actual dimentionality
// requires display:block with an explicitly defined H & W
$tremulaContainer = $('.tremulaContainer');
//this creates a hook to a new Tremula instance
var tremula = new Tremula();
//Create a config object -- this is how most default behaivior is set.
//see updateConfig(prop_val_object,refreshStreamFlag) method to change properties of a running instance
var config = {
//Size of the static axis in pixels
//If your scroll axis is set to 'x' then this will be the normalized height of your content blocks.
//If your scroll axis is set to 'y' then this will be the normalized width of your content blocks.
itemConstraint :250,//px
//Margin in px added to each side of each content item
itemMargins :[10,10],//x (left & right), y (top & bottom) in px
//Display offset of static axis (static axis is the non-scrolling dimention)
staticAxisOffset :0,//px
//Display offset of scroll axis (this is the amount of scrollable area added before the first content block)
scrollAxisOffset :20,//px
//Sets the scroll axis 'x'|'y'.
//NOTE: projections generally only work with one scroll axis
//when changeing this value, make sure to use a compatible projection
scrollAxis :'x',//'x'|'y'
//surfaceMap is the projection/3d-effect which will be used to display grid content
//following is a list of built-in projections with their corresponding scroll direction
//NOTE: Using a projection with an incompatible Grid or Grid-Direction will result in-not-so awesome results
//----------------------
// (x or y) xyPlain
// (x) streamHorizontal
// (y) pinterest
// (x) mountain
// (x) turntable
// (x) enterTheDragon
// (x) userProjection <--
//----------------------
surfaceMap :userProjection,//tremula.projections.streamHorizontal,
//how many rows (or colums) to display. note: this is zero based -- so a value of 0 means there will be one row/column
staticAxisCount :0,//zero based
//the grid that will be used to project content
//NOTE: Generally, this will stay the same and various surface map projections
//will be used to create various 3d positioning effects
defaultLayout :tremula.layouts.xyPlain,
//it does not look like this actually got implemented so, don't worry about it ;)
itemPreloading :true,
//enables the item-level momentum envelope
itemEasing :false,
//enables looping with the current seet of results
isLooping :false,
//if item-level easing is enabled, it will use the following parameters
//NOTE: this is experimental. This effect can make people queasy.
itemEasingParams :{
touchCurve :tremula.easings.easeOutCubic,
swipeCurve :tremula.easings.easeOutCubic,
transitionCurve :tremula.easings.easeOutElastic,
easeTime :500,
springLimit :40 //in px
},
//method called after each frame is painted. Passes internal parameter object.
//see fn definition below
onChangePub : doScrollEvents,
//content/stream data can optionally be passed in on init()
data : null,
// lastContentBlock enables a persistant content block to exist at the end of the stream at all times.
// Common use case is to target $('.lastContentItem') with a conditional loading spinner when API is churning.
lastContentBlock : {
template :'<div class="lastContentItem"></div>',
layoutType :'tremulaBlockItem',
noScaling:true,
w:300,
h:300,
isLastContentBlock:true,
adapter:tremula.dataAdapters.TremulaItem
},
//dafault data adapter method which is called on each data item -- this is used if none is supplied during an import operation
//enables easy adaptation of arbitrary API data formats -- see flickr example
adapter :null
};
//initalize the tremula instance with 3 parameters:
//a DOM container, a config object, and a parent context
tremula.init($tremulaContainer,config,this);
//return the tremula hook
return tremula;
}
//This method is called on each paint frame thus enabling low level behaivior control
//it receives a single parameter object of internal instance states
//NOTE: below is a simple example of infinate scrolling where new item
//requests are made when the user scrolls past the existing 70% mark.
//
//Another option here is multiple tremula instancechaining i.e. follow the scroll events of another tremula instance.
//use case of this may be one tremula displaying close up data view while another may be an overview.
function doScrollEvents(o){
if(o.scrollProgress>.7){
if(!tremula.cache.endOfScrollFlag){
tremula.cache.endOfScrollFlag = true;
pageCtr++;
loadFlickr();
console.log('END OF SCROLL!')
}
}
};
//Basic example of API integration
//=================================
//DATA FUNCTIONS OF NOTE:
//tremula.refreshData(returned_set_array,dataAdapter)//replaces current data set with returned_set_array
//tremula.appendData(returned_set_array,dataAdapter)//appends current data set with returned_set_array
//tremula.insertData(returned_set_array,dataAdapter)//prepends current data set with returned_set_array
//=================================
/* SIZE SUFFIX FOR FLICKR IMAGE URLS ===> must be set in method below also
s small square 75x75
q large square 150x150
t thumbnail, 100 on longest side
m small, 240 on longest side
n small, 320 on longest side
- medium, 500 on longest side
z medium 640, 640 on longest side
c medium 800, 800 on longest side†
b large, 1024 on longest side*
o original image, either a jpg, gif or png, depending on source format
*/
var pageCtr = 1;
function loadFlickr(){
var dataUrl = 'https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=c149b994c54c114bd7836b61539eec2e&tags=street+art&format=json&page='+pageCtr+'&extras=url_n';
$.ajax({
url:dataUrl
,dataType: 'jsonp'
,jsonp: 'jsoncallback'
})
.done(function(res){
console.log('API success',res);
var rs = res.photos.photo.filter(function(o,i){return o.height_n > o.width_n * .5});//filter out any with a really wide aspect ratio.
tremula.appendData(rs,flickrDataAdapter);//flicker
tremula.cache.endOfScrollFlag = false;
})
.fail( function(d,config,err){ console.log('API FAIL. '+err) })
}
// DATA ADAPTER EXAMPLE
//=====================
// flickrDataAdapter() is for use with the flickr API
// https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=c149b994c54c114bd7836b61539eec2e&tags=sky%2C+night%2C+day&format=json&page=1
/* SIZE SUFFIX FOR FLICKR IMAGE URLS ===> must be set in above method also
s small square 75x75
q large square 150x150
t thumbnail, 100 on longest side
m small, 240 on longest side
n small, 320 on longest side
- medium, 500 on longest side
z medium 640, 640 on longest side
c medium 800, 800 on longest side†
b large, 1024 on longest side*
o original image, either a jpg, gif or png, depending on source format
*/
function flickrDataAdapter(data,env){
this.data = data;
this.w = this.width = data.width_n;
this.h = this.height = data.height_n;
this.imgUrl = data.url_n;
this.auxClassList = "flickrRS";//stamp each mapped item with map ID
this.template = this.data.template||('<img draggable="false" class="moneyShot" onload="imageLoaded(this)" src=""/>');
}
// updateConfig() enables updating of configuration parameters after an instance is running.
// adding an optional true parameter will force a tremula grid redraw with the new parameter in effect
// ----------------------------
// EXAMPLE: tremula.Grid.updateConfig({itemConstraint:100},true);
// ----------------------------
// Use toggleScrollAxis() to set the scrollAxis,
// see: surfaceMap projection compatibility list above to ensure the projection is compatible with the scrollAxis value
// ----------------------------
// EXAMPLE: tremula.Grid.toggleScrollAxis('y');
// ----------------------------
function applyBoxClick(){
$('.tremulaContainer').on('tremulaItemSelect',function(gestureEvt,domEvt){
// console.log(gestureEvt,domEvt)
var
$e = $(domEvt.target);
t = $e.closest('.gridBox')[0];
if(t){
var data = $.data(t).model.model.data;
}
if(data)alert(JSON.stringify(data));
})
}
//====================
// This is a custom Projection template which allows you to specify your own bezier path
// To use, modify the above configuration @ surfaceMap --> surfaceMap : userProjection,
//EXPERIMENTAL! Generally, this works, But it's not particularly tested. Some paths may not work as expected.
//Please file bugs to https://github.com/garris/TremulaJS/issues
// ALSO: This currently only works in horizontal mode. Vertical coming soon.
// Handy bezier editor/visualizer here --> https://www.desmos.com/calculator/iaf7aha9yl
var userPath = [
{x:0,y:.2},
{x:.5,y:.5},
{x:.5,y:.8},
{x:1,y:.5}
];
function userProjection(x,y){
var curve = userPath;
var
grid0 = this.parent.gridDims[0],
grid1 = this.parent.gridDims[1],
axisLength = this.parent.currentGridContentDims,
tRamp = this.waves.tailRamp,
hRamp = this.waves.headRamp,
tri = this.waves.triangle,
xo,
yo;
var xyFactor = [
grid0,
grid1
];
var cubicBezier = jsBezier.factorCurveBy(curve,xyFactor);
var p = jsBezier.pointOnCurve(cubicBezier, tRamp);
var g = jsBezier.gradientAtPoint(cubicBezier, tRamp);
var xo = p.x - (this.dims[0]*.5);
var yo = grid1 - p.y - (this.dims[1]*.5) - (((axisLength[1]-this.dims[1])*.5) - y - this.itemMargins[1]);
var zo = 0;
this.e.style.transformOrigin = this.e.style.webkitTransformOrigin = this.e.style.MozTransformOrigin = '50%';
this.e.style.transform = this.e.style.MozTransform = this.e.style.webkitTransform = 'translate3d(' + xo + 'px,' + yo +'px, ' + zo + 'px)' + ' rotateZ('+g*40+120+'deg)';
var z = 10000-this.index;
this.e.style.zIndex = z;
this.e.style.opacity = 1;
//this.e.style.zIndex = Math.abs(Math.floor(tri*100));
this.pPos = [x,y];
}
@import url(http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz);
body{
font-family: 'Yanone Kaffeesatz', sans-serif;
background-color: #ddd;
}
*{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
a{color:inherit;text-decoration:none;}
h1{
color: rgba(0, 0, 0, 0.19);
z-index: 0;
position: absolute;
right: 20px;
bottom: 0;
font-size: 40px;
line-height:50px;
padding: 0;
margin: 0;
text-shadow: 0px 1px 1px rgba(255,255,255,1);
display:inline-block;
}
.plug{padding:15px 20px;position:absolute;}
.cta{
font-size:25px;
color:#666;
padding: 0;
margin: 0;
position: absolute;
left: 20px;
bottom: 0;
line-height:40px;
}
/* === Tremula Styles === */
/* Note: any gridBox border added must be compensated for by adding negative margin */
.gridBox{
border:#fff 10px solid;
margin: -10px 0 0 -10px;
}
.moneyShot{
width:100%;
}
/* .tremulaContainer must have actual dimentionality (set H&W) */
.tremulaContainer{
height:60%;
height:calc(100% - 120px);
width:90%;
position:relative;
top:20px;
left:0;
z-index: 1;
margin:25px auto;
border: black solid 1px;
}
.pageLayoutContainer{
height:100%;
width:100%;
position:fixed;
top:0;
left:0;
margin:0;
border: black solid 1px;
}

TremulaJS Boilerplate

TremulaJS is a client-side javascript UI component providing Bézier-based content-stream interactions with momentum & physics effects for mouse, scroll and and touch UIs.

This Pen allows you to experiment using different config file settings.

A Pen by Garris on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment