392 lines
13 KiB
JavaScript
392 lines
13 KiB
JavaScript
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, "");
|
|
}
|
|
});
|