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.
| <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 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.