jQuery(document).ready(function ($) { var timelines = $('.cd-horizontal-timeline'), eventsMinDistance = 60 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) } } function updateTimelinePosition(string, event, timelineComponents) { //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['eventsWrapper'].find('a.selected'), timelineComponents['fillingLine'], totalWidth) updateTimelinePosition('next', timelineComponents['eventsWrapper'].find('a.selected'), timelineComponents) 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 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, '') } })