jquery(document).ready(function($){ var timelines = $('.cd-horizontal-timeline'), eventsmindistance = 100; (timelines.length > 0) && inittimeline(timelines); function inittimeline(timelines) { timelines.each(function(){ var timeline = $(this), timelinecomponents = {}; //cache timeline components timelinecomponents['timelinewrapper'] = timeline.find('.events-wrapper'); timelinecomponents['eventswrapper'] = timelinecomponents['timelinewrapper'].children('.events'); timelinecomponents['fillingline'] = timelinecomponents['eventswrapper'].children('.filling-line'); timelinecomponents['timelineevents'] = timelinecomponents['eventswrapper'].find('a'); timelinecomponents['timelinedates'] = parsedate(timelinecomponents['timelineevents']); timelinecomponents['eventsminlapse'] = minlapse(timelinecomponents['timelinedates']); timelinecomponents['timelinenavigation'] = timeline.find('.cd-timeline-navigation'); timelinecomponents['eventscontent'] = timeline.children('.events-content'); //assign a left postion to the single events along the timeline setdateposition(timelinecomponents, eventsmindistance); //assign a width to the timeline var timelinetotwidth = settimelinewidth(timelinecomponents, eventsmindistance); //the timeline has been initialize - show it timeline.addclass('loaded'); //detect click on the next arrow timelinecomponents['timelinenavigation'].on('click', '.next', function(event){ event.preventdefault(); updateslide(timelinecomponents, timelinetotwidth, 'next'); }); //detect click on the prev arrow timelinecomponents['timelinenavigation'].on('click', '.prev', function(event){ event.preventdefault(); updateslide(timelinecomponents, timelinetotwidth, 'prev'); }); //detect click on the a single event - show new event content timelinecomponents['eventswrapper'].on('click', 'a', function(event){ event.preventdefault(); timelinecomponents['timelineevents'].removeclass('selected'); $(this).addclass('selected'); updateolderevents($(this)); updatefilling($(this), timelinecomponents['fillingline'], timelinetotwidth); updatevisiblecontent($(this), timelinecomponents['eventscontent']); }); //on swipe, show next/prev event content timelinecomponents['eventscontent'].on('swipeleft', function(){ var mq = checkmq(); ( mq == 'mobile' ) && shownewcontent(timelinecomponents, timelinetotwidth, 'next'); }); timelinecomponents['eventscontent'].on('swiperight', function(){ var mq = checkmq(); ( mq == 'mobile' ) && shownewcontent(timelinecomponents, timelinetotwidth, 'prev'); }); //keyboard navigation $(document).keyup(function(event){ if(event.which=='37' && elementinviewport(timeline.get(0)) ) { shownewcontent(timelinecomponents, timelinetotwidth, 'prev'); } else if( event.which=='39' && elementinviewport(timeline.get(0))) { shownewcontent(timelinecomponents, timelinetotwidth, 'next'); } }); }); } function updateslide(timelinecomponents, timelinetotwidth, string) { //retrieve translatex value of timelinecomponents['eventswrapper'] var translatevalue = gettranslatevalue(timelinecomponents['eventswrapper']), wrapperwidth = number(timelinecomponents['timelinewrapper'].css('width').replace('px', '')); //translate the timeline to the left('next')/right('prev') (string == 'next') ? translatetimeline(timelinecomponents, translatevalue - wrapperwidth + eventsmindistance, wrapperwidth - timelinetotwidth) : translatetimeline(timelinecomponents, translatevalue + wrapperwidth - eventsmindistance); } function shownewcontent(timelinecomponents, timelinetotwidth, string) { //go from one event to the next/previous one var visiblecontent = timelinecomponents['eventscontent'].find('.selected'), newcontent = ( string == 'next' ) ? visiblecontent.next() : visiblecontent.prev(); if ( newcontent.length > 0 ) { //if there's a next/prev event - show it var selecteddate = timelinecomponents['eventswrapper'].find('.selected'), newevent = ( string == 'next' ) ? selecteddate.parent('li').next('li').children('a') : selecteddate.parent('li').prev('li').children('a'); updatefilling(newevent, timelinecomponents['fillingline'], timelinetotwidth); updatevisiblecontent(newevent, timelinecomponents['eventscontent']); newevent.addclass('selected'); selecteddate.removeclass('selected'); updateolderevents(newevent); updatetimelineposition(string, newevent, timelinecomponents, timelinetotwidth); } } function updatetimelineposition(string, event, timelinecomponents, timelinetotwidth) { //translate timeline to the left/right according to the position of the selected event var eventstyle = window.getcomputedstyle(event.get(0), null), eventleft = number(eventstyle.getpropertyvalue("left").replace('px', '')), timelinewidth = number(timelinecomponents['timelinewrapper'].css('width').replace('px', '')), timelinetotwidth = number(timelinecomponents['eventswrapper'].css('width').replace('px', '')); var timelinetranslate = gettranslatevalue(timelinecomponents['eventswrapper']); if( (string == 'next' && eventleft > timelinewidth - timelinetranslate) || (string == 'prev' && eventleft < - timelinetranslate) ) { translatetimeline(timelinecomponents, - eventleft + timelinewidth/2, timelinewidth - timelinetotwidth); } } function translatetimeline(timelinecomponents, value, totwidth) { var eventswrapper = timelinecomponents['eventswrapper'].get(0); value = (value > 0) ? 0 : value; //only negative translate value value = ( !(typeof totwidth === 'undefined') && value < totwidth ) ? totwidth : value; //do not translate more than timeline width settransformvalue(eventswrapper, 'translatex', value+'px'); //update navigation arrows visibility (value == 0 ) ? timelinecomponents['timelinenavigation'].find('.prev').addclass('inactive') : timelinecomponents['timelinenavigation'].find('.prev').removeclass('inactive'); (value == totwidth ) ? timelinecomponents['timelinenavigation'].find('.next').addclass('inactive') : timelinecomponents['timelinenavigation'].find('.next').removeclass('inactive'); } function updatefilling(selectedevent, filling, totwidth) { //change .filling-line length according to the selected event var eventstyle = window.getcomputedstyle(selectedevent.get(0), null), eventleft = eventstyle.getpropertyvalue("left"), eventwidth = eventstyle.getpropertyvalue("width"); eventleft = number(eventleft.replace('px', '')) + number(eventwidth.replace('px', ''))/2; var scalevalue = eventleft/totwidth; settransformvalue(filling.get(0), 'scalex', scalevalue); } function setdateposition(timelinecomponents, min) { for (i = 0; i < timelinecomponents['timelinedates'].length; i++) { var distance = daydiff(timelinecomponents['timelinedates'][0], timelinecomponents['timelinedates'][i]), distancenorm = math.round(distance/timelinecomponents['eventsminlapse']) + 2; timelinecomponents['timelineevents'].eq(i).css('left', distancenorm*min+'px'); } } function settimelinewidth(timelinecomponents, width) { var timespan = daydiff(timelinecomponents['timelinedates'][0], timelinecomponents['timelinedates'][timelinecomponents['timelinedates'].length-1]), timespannorm = timespan/timelinecomponents['eventsminlapse'], timespannorm = math.round(timespannorm) + 4, totalwidth = timespannorm*width; timelinecomponents['eventswrapper'].css('width', totalwidth+'px'); updatefilling(timelinecomponents['timelineevents'].eq(0), timelinecomponents['fillingline'], totalwidth); return totalwidth; } function updatevisiblecontent(event, eventscontent) { var eventdate = event.data('date'), visiblecontent = eventscontent.find('.selected'), selectedcontent = eventscontent.find('[data-date="'+ eventdate +'"]'), selectedcontentheight = selectedcontent.height(); if (selectedcontent.index() > visiblecontent.index()) { var classenetering = 'selected enter-right', classleaving = 'leave-left'; } else { var classenetering = 'selected enter-left', classleaving = 'leave-right'; } selectedcontent.attr('class', classenetering); visiblecontent.attr('class', classleaving).one('webkitanimationend oanimationend msanimationend animationend', function(){ visiblecontent.removeclass('leave-right leave-left'); selectedcontent.removeclass('enter-left enter-right'); }); eventscontent.css('height', selectedcontentheight+'px'); } function updateolderevents(event) { event.parent('li').prevall('li').children('a').addclass('older-event').end().end().nextall('li').children('a').removeclass('older-event'); } function gettranslatevalue(timeline) { var timelinestyle = window.getcomputedstyle(timeline.get(0), null), timelinetranslate = timelinestyle.getpropertyvalue("-webkit-transform") || timelinestyle.getpropertyvalue("-moz-transform") || timelinestyle.getpropertyvalue("-ms-transform") || timelinestyle.getpropertyvalue("-o-transform") || timelinestyle.getpropertyvalue("transform"); if( timelinetranslate.indexof('(') >=0 ) { var timelinetranslate = timelinetranslate.split('(')[1]; timelinetranslate = timelinetranslate.split(')')[0]; timelinetranslate = timelinetranslate.split(','); var translatevalue = timelinetranslate[4]; } else { var translatevalue = 0; } return number(translatevalue); } function settransformvalue(element, property, value) { element.style["-webkit-transform"] = property+"("+value+")"; element.style["-moz-transform"] = property+"("+value+")"; element.style["-ms-transform"] = property+"("+value+")"; element.style["-o-transform"] = property+"("+value+")"; element.style["transform"] = property+"("+value+")"; } //based on http://stackoverflow.com/questions/542938/how-do-i-get-the-number-of-days-between-two-dates-in-javascript function parsedate(events) { var datearrays = []; events.each(function(){ var datecomp = $(this).data('date').split('/'), newdate = new date(datecomp[2], datecomp[1]-1, datecomp[0]); datearrays.push(newdate); }); return datearrays; } function parsedate2(events) { var datearrays = []; events.each(function(){ var singledate = $(this), datecomp = singledate.data('date').split('t'); if( datecomp.length > 1 ) { //both dd/mm/year and time are provided var daycomp = datecomp[0].split('/'), timecomp = datecomp[1].split(':'); } else if( datecomp[0].indexof(':') >=0 ) { //only time is provide var daycomp = ["2000", "0", "0"], timecomp = datecomp[0].split(':'); } else { //only dd/mm/year var daycomp = datecomp[0].split('/'), timecomp = ["0", "0"]; } var newdate = new date(daycomp[2], daycomp[1]-1, daycomp[0], timecomp[0], timecomp[1]); datearrays.push(newdate); }); return datearrays; } function daydiff(first, second) { return math.round((second-first)); } function minlapse(dates) { //determine the minimum distance among events var datedistances = []; for (i = 1; i < dates.length; i++) { var distance = daydiff(dates[i-1], dates[i]); datedistances.push(distance); } return math.min.apply(null, datedistances); } /* how to tell if a dom element is visible in the current viewport? http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport */ function elementinviewport(el) { var top = el.offsettop; var left = el.offsetleft; var width = el.offsetwidth; var height = el.offsetheight; while(el.offsetparent) { el = el.offsetparent; top += el.offsettop; left += el.offsetleft; } return ( top < (window.pageyoffset + window.innerheight) && left < (window.pagexoffset + window.innerwidth) && (top + height) > window.pageyoffset && (left + width) > window.pagexoffset ); } function checkmq() { //check if mobile or desktop device return window.getcomputedstyle(document.queryselector('.cd-horizontal-timeline'), '::before').getpropertyvalue('content').replace(/'/g, "").replace(/"/g, ""); } });