Lots of fixes to storyview mechanism

And a new scroller mechanism
This commit is contained in:
Jeremy Ruston 2012-07-07 17:14:50 +01:00
parent a21d96adc1
commit f412dd19d9
7 changed files with 241 additions and 150 deletions

View File

@ -17,8 +17,6 @@ exports.info = {
params: {
story: {byName: "default", type: "text"}, // Actually a tiddler, but we don't want it to be a dependency
history: {byName: "default", type: "text"}, // Actually a tiddler, but we don't want it to be a dependency
defaultViewTemplate: {byName: true, type: "tiddler"},
defaultEditTemplate: {byName: true, type: "tiddler"},
set: {byName: true, type: "tiddler"}
}
};
@ -64,8 +62,7 @@ exports.eventMap["tw-navigate"] = function(event) {
// Update the story tiddler if specified
if(this.hasParameter("story")) {
this.getStory();
var template = this.params.defaultViewTemplate || "$:/templates/ViewTemplate",
t,tiddler,slot;
var t,tiddler,slot;
// See if the tiddler is already there
for(t=0; t<this.story.tiddlers.length; t++) {
if(this.story.tiddlers[t].title === event.navigateTo) {
@ -102,13 +99,46 @@ exports.eventMap["tw-navigate"] = function(event) {
this.history.stack.push({
title: event.navigateTo,
fromTitle: event.navigateFromTitle,
fromPosition: event.navigateFrom.getNodeBounds()
fromPosition: event.navigateFrom.getNodeBounds(),
scrollPosition: $tw.utils.getScrollPosition()
});
this.saveHistory();
event.stopPropagation();
return false;
};
// Navigate to a specified tiddler
exports.eventMap["tw-NavigateBack"] = function(event) {
// Pop a record record off the top of the history stack
this.getHistory();
if(this.history.stack.length < 2) {
return false; // Bail if there is not enough entries on the history stack
}
var fromHistoryInfo = this.history.stack.pop(),
toHistoryInfo = this.history.stack[this.history.stack.length-1];
this.saveHistory();
// Make sure that the tiddler we're navigating back to is open in the story
if(this.hasParameter("story") && toHistoryInfo) {
this.getStory();
var t,tiddler,slot;
// See if the tiddler is already there
for(t=0; t<this.story.tiddlers.length; t++) {
if(this.story.tiddlers[t].title === toHistoryInfo.title) {
tiddler = t;
}
}
// If not we need to add it
if(tiddler === undefined) {
// Add the tiddler
this.story.tiddlers.splice(slot,0,{title: toHistoryInfo.title});
// Save the story
this.saveStory();
}
}
event.stopPropagation();
return false;
};
// Place a tiddler in edit mode
exports.eventMap["tw-EditTiddler"] = function(event) {
if(this.hasParameter("story")) {
@ -204,7 +234,7 @@ exports.executeMacro = function() {
this.content[t].execute(this.parents,this.tiddlerTitle);
}
return $tw.Tree.Element("div",attributes,this.content,{
events: ["tw-navigate","tw-EditTiddler","tw-SaveTiddler","tw-CloseTiddler"],
events: ["tw-navigate","tw-EditTiddler","tw-SaveTiddler","tw-CloseTiddler","tw-NavigateBack"],
eventHandler: this
});
};

View File

@ -19,7 +19,11 @@ And the history tiddler is the stack of tiddlers that were navigated to in turn:
{
stack: [
{title: <string>}
{
title: <string>,
fromTitle: <string>,
fromPosition: {bottom: <num>, height: <num>, top: <num>, right: <num>, left: <num>, width: <num>}
}
]
}
@ -132,6 +136,9 @@ exports.removeStoryElement = function(storyElementIndex) {
// Only delete the DOM element if the storyview.remove() returned false
storyElement.domNode.parentNode.removeChild(storyElement.domNode);
}
} else {
// Always delete the story element if we didn't invoke the storyview
storyElement.domNode.parentNode.removeChild(storyElement.domNode);
}
// Then delete the actual renderer node
this.storyNode.children.splice(storyElementIndex,1);
@ -169,8 +176,10 @@ exports.executeMacro = function() {
};
exports.postRenderInDom = function() {
// Reset the record of the previous history stack
this.prevHistory = {stack: []};
// Get the history object and use it to set the previous history
this.getHistory();
this.prevHistory = this.history;
this.history = null;
// Instantiate the story view
var storyviewName;
if(this.hasParameter("storyviewTiddler")) {
@ -278,7 +287,7 @@ exports.processHistoryChange = function() {
// Read the history tiddler
this.getHistory();
if(this.storyview) {
var t,index,
var t,indexTo,indexFrom,
topCommon = Math.min(this.history.stack.length,this.prevHistory.stack.length);
// Find the common heritage of the new history stack and the previous one
for(t=0; t<topCommon; t++) {
@ -289,16 +298,21 @@ exports.processHistoryChange = function() {
}
// We now navigate backwards through the previous history to get back to the common ancestor
for(t=this.prevHistory.stack.length-1; t>=topCommon; t--) {
index = this.findStoryElementByTitle(0,this.prevHistory.stack[t].title);
if(index !== undefined && this.storyview.navigateBack) {
this.storyview.navigateBack(this.storyNode.children[index],this.history.stack[t]);
indexTo = this.findStoryElementByTitle(0,this.prevHistory.stack[t].fromTitle);
indexFrom = this.findStoryElementByTitle(0,this.prevHistory.stack[t].title);
// Call the story view if it defines a navigateBack() method
if(indexTo !== undefined && indexFrom !== undefined && this.storyview.navigateBack) {
this.storyview.navigateBack(this.storyNode.children[indexTo],this.storyNode.children[indexFrom],this.prevHistory.stack[t]);
}
}
// And now we navigate forwards through the new history to get to the latest tiddler
for(t=topCommon; t<this.history.stack.length; t++) {
index = this.findStoryElementByTitle(0,this.history.stack[t].title);
if(index !== undefined && this.storyview.navigateForward) {
this.storyview.navigateForward(this.storyNode.children[index],this.history.stack[t]);
indexTo = this.findStoryElementByTitle(0,this.history.stack[t].title);
indexFrom = this.findStoryElementByTitle(0,this.history.stack[t].fromTitle);
if(indexTo !== undefined && this.storyview.navigateForward) {
this.storyview.navigateForward(this.storyNode.children[indexTo],
indexFrom !== undefined ? this.storyNode.children[indexFrom] : undefined,
this.history.stack[t]);
}
}
}

View File

@ -31,7 +31,7 @@ ClassicScroller.prototype.remove = function(storyElementNode) {
}
},false);
// Animate the closure
var d = ($tw.config.preferences.animationDuration/1000).toFixed(8) + "s";
var d = $tw.config.preferences.animationDuration + "ms";
wrapperElement.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, " +
"opacity " + d + " ease-out, " +
"height " + d + " ease-in-out";
@ -48,12 +48,12 @@ ClassicScroller.prototype.remove = function(storyElementNode) {
return true;
};
ClassicScroller.prototype.navigateBack = function(storyElementNode,historyInfo) {
$tw.utils.scrollIntoView(storyElementNode.domNode);
ClassicScroller.prototype.navigateBack = function(toStoryElement,fromStoryElement,historyInfo) {
$tw.scroller.scrollIntoView(toStoryElement.domNode);
};
ClassicScroller.prototype.navigateForward = function(storyElementNode,historyInfo) {
$tw.utils.scrollIntoView(storyElementNode.domNode);
ClassicScroller.prototype.navigateForward = function(toStoryElement,fromStoryElement,historyInfo) {
$tw.scroller.scrollIntoView(toStoryElement.domNode);
};
exports.classic = ClassicScroller;

View File

@ -30,13 +30,23 @@ function SidewaysView(story) {
}
}
/*
Visualise navigation to a new tiddler
toStoryElementNode: tree node of the tiddler macro we're navigating to
fromStoryElementNode: optionally, tree node of the tiddler we're navigating from
historyInfo: record from the history tiddler corresponding to this navigation
*/
SidewaysView.prototype.navigateForward = function(toStoryElement,fromStoryElement,historyInfo) {
$tw.scroller.scrollIntoView(toStoryElement.domNode);
};
/*
Visualise insertion of the specified tiddler macro, optionally specifying a source node for the visualisation
storyElementNode: tree node of the tiddler macro we're navigating to
*/
SidewaysView.prototype.insert = function(storyElementNode) {
setStoryElementStyles(storyElementNode.domNode);
$tw.utils.scrollIntoView(storyElementNode.domNode);
$tw.scroller.scrollIntoView(storyElementNode.domNode);
};
SidewaysView.prototype.remove = function(storyElementNode) {
@ -48,7 +58,7 @@ SidewaysView.prototype.remove = function(storyElementNode) {
targetElement.parentNode.insertBefore(wrapperElement,targetElement);
wrapperElement.appendChild(targetElement);
// Animate the closure
var d = ($tw.config.preferences.animationDuration/1000).toFixed(8) + "s";
var d = $tw.config.preferences.animationDuration + "ms";
wrapperElement.style.display = "inline-block";
wrapperElement.style[$tw.browser.transformorigin] = "0% 0%";
wrapperElement.style[$tw.browser.transform] = "translateY(0px)";

View File

@ -3,7 +3,11 @@ title: $:/core/modules/macros/story/views/zoomin.js
type: application/javascript
module-type: storyview
A storyview that shows a single tiddler and navigates by zooming into links
A storyview that shows a single tiddler and navigates by zooming into links.
To do this, the story wrapper is set to `position:relative` and then each of the story elements to `position:absolute`. This results in all of the tiddlers being stacked on top of one another flush with the top left of the story wrapper.
Navigating between tiddlers is accomplished by switching the story nodes between `display:block` and `display:none`, but the implementation is considerably more complex due to the animation.
\*/
(function(){
@ -16,6 +20,8 @@ function Zoomin(story) {
// Save the story
this.story = story;
this.storyNode = this.story.child.domNode;
// Set the current tiddler
this.currentTiddler = this.story.child.children[0];
// Make all the tiddlers position absolute, and hide all but the first one
this.storyNode.style.position = "relative";
for(var t=0; t<this.storyNode.children.length; t++) {
@ -24,10 +30,6 @@ function Zoomin(story) {
}
this.storyNode.children[t].style.position = "absolute";
}
// Record the current tiddler node
this.currTiddler = this.story.child.children[0];
// Set up the stack of previously viewed tiddlers
this.prevTiddlers = [this.currTiddler.children[0].params.target];
}
/*
@ -55,128 +57,120 @@ function findTitleNode(node) {
}
/*
Visualise removal of the the specified tiddler macro, optionally specifying a source node for the visualisation
storyElementNode: tree node of the tiddler macro we're navigating to
Visualise navigation to a new tiddler
toStoryElementNode: tree node of the tiddler macro we're navigating to
fromStoryElementNode: optionally, tree node of the tiddler we're navigating from
historyInfo: record from the history tiddler corresponding to this navigation
*/
Zoomin.prototype.navigateForward = function(storyElementNode,historyInfo) {
// Do nothing if the target tiddler is already the current tiddler
if(storyElementNode === this.currTiddler) {
return;
}
// Make the new tiddler be position absolute and visible
storyElementNode.domNode.style.position = "absolute";
storyElementNode.domNode.style.display = "block";
storyElementNode.domNode.style[$tw.browser.transformorigin] = "0 0";
storyElementNode.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(1)";
storyElementNode.domNode.style[$tw.browser.transition] = "none";
Zoomin.prototype.navigateForward = function(toStoryElement,fromStoryElement,historyInfo) {
// Make the new tiddler be position absolute and visible so that we can measure it
toStoryElement.domNode.style.position = "absolute";
toStoryElement.domNode.style.display = "block";
toStoryElement.domNode.style[$tw.browser.transformorigin] = "0 0";
toStoryElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(1)";
toStoryElement.domNode.style[$tw.browser.transition] = "none";
toStoryElement.domNode.style.opacity = "0.0";
// Get the position of the source node, or use the centre of the window as the source position
var sourceBounds = historyInfo.fromPosition || {
left: window.innerWidth/2 - 2,
top: window.innerHeight/2 - 2,
width: 4,
height: 4
width: window.innerWidth/8,
height: window.innerHeight/8
};
// Try to find the title node in the target tiddler
var titleNode = findTitleNode(storyElementNode) || storyElementNode;
var titleNode = findTitleNode(toStoryElement) || toStoryElement,
titleBounds = titleNode.getNodeBounds();
// Compute the transform for the target tiddler to make the title lie over the source rectange
var targetBounds = storyElementNode.getNodeBounds(),
titleBounds = titleNode.getNodeBounds(),
var targetBounds = toStoryElement.getNodeBounds(),
scale = sourceBounds.width / titleBounds.width,
x = sourceBounds.left - targetBounds.left - (titleBounds.left - targetBounds.left) * scale,
y = sourceBounds.top - targetBounds.top - (titleBounds.top - targetBounds.top) * scale;
// Transform the target tiddler
storyElementNode.domNode.style[$tw.browser.transform] = "translateX(" + x + "px) translateY(" + y + "px) scale(" + scale + ")";
// Transform the target tiddler to its starting position
toStoryElement.domNode.style[$tw.browser.transform] = "translateX(" + x + "px) translateY(" + y + "px) scale(" + scale + ")";
// Get the animation duration
var d = ($tw.config.preferences.animationDuration/1000).toFixed(8) + "s";
var d = $tw.config.preferences.animationDuration + "ms";
// Apply the ending transitions with a timeout to ensure that the previously applied transformations are applied first
var self = this,
currTiddler = this.currTiddler;
prevCurrentTiddler = this.currentTiddler;
this.currentTiddler = toStoryElement;
$tw.utils.nextTick(function() {
// Transform the target tiddler
var currTiddlerBounds = currTiddler.getNodeBounds(),
x = (currTiddlerBounds.left - targetBounds.left),
y = (currTiddlerBounds.top - targetBounds.top);
storyElementNode.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
storyElementNode.domNode.style.opacity = "1.0";
storyElementNode.domNode.style[$tw.browser.transform] = "translateX(" + x + "px) translateY(" + y + "px) scale(1)";
storyElementNode.domNode.style.zIndex = "500";
// Transform the current tiddler
var scale = titleBounds.width / sourceBounds.width;
x = titleBounds.left - targetBounds.left - (sourceBounds.left - currTiddlerBounds.left) * scale;
y = titleBounds.top - targetBounds.top - (sourceBounds.top - currTiddlerBounds.top) * scale;
currTiddler.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
currTiddler.domNode.style.opacity = "0.0";
currTiddler.domNode.style[$tw.browser.transformorigin] = "0 0";
currTiddler.domNode.style[$tw.browser.transform] = "translateX(" + x + "px) translateY(" + y + "px) scale(" + scale + ")";
currTiddler.domNode.style.zIndex = "0";
// Transform the target tiddler to its natural size
toStoryElement.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
toStoryElement.domNode.style.opacity = "1.0";
toStoryElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(1)";
toStoryElement.domNode.style.zIndex = "500";
// Transform the previous tiddler out of the way and then hide it
if(prevCurrentTiddler && prevCurrentTiddler !== toStoryElement) {
var scale = titleBounds.width / sourceBounds.width;
x = titleBounds.left - targetBounds.left - (sourceBounds.left - targetBounds.left) * scale;
y = titleBounds.top - targetBounds.top - (sourceBounds.top - targetBounds.top) * scale;
prevCurrentTiddler.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
prevCurrentTiddler.domNode.style.opacity = "0.0";
prevCurrentTiddler.domNode.style[$tw.browser.transformorigin] = "0 0";
prevCurrentTiddler.domNode.style[$tw.browser.transform] = "translateX(" + x + "px) translateY(" + y + "px) scale(" + scale + ")";
prevCurrentTiddler.domNode.style.zIndex = "0";
var eventHandler = function(event) {
// Hide the DOM node when the transition is over
if(self.currentTiddler !== prevCurrentTiddler) {
prevCurrentTiddler.domNode.style.display = "none";
}
prevCurrentTiddler.domNode.removeEventListener($tw.browser.transitionEnd,eventHandler,true);
};
prevCurrentTiddler.domNode.addEventListener($tw.browser.transitionEnd,eventHandler,true);
}
// Scroll the target into view
$tw.scroller.scrollIntoView(toStoryElement.domNode);
});
// Record the new current tiddler
this.currTiddler = storyElementNode;
// Save the tiddler in the stack
this.prevTiddlers.push(storyElementNode.children[0].params.target);
};
/*
Visualise removing a tiddler
Visualise navigating back to the previous tiddler
toStoryElement: story element being navigated back to
fromStoryElement: story element being navigated back from
historyInfo: member of the history stack[] array being navigated back through
*/
Zoomin.prototype.remove = function(storyElementNode) {
// Remove the last entry from the navigation stack, which will be to navigate to the current tiddler
this.prevTiddlers.pop();
// Find the top entry in the navigation stack that still exists
var storyElementIndex,storyElement;
while(this.prevTiddlers.length > 0) {
var title = this.prevTiddlers.pop();
storyElementIndex = this.story.findStoryElementByTitle(0,title);
if(storyElementIndex !== undefined) {
break;
}
}
if(storyElementIndex !== undefined) {
storyElement = this.story.storyNode.children[storyElementIndex];
}
Zoomin.prototype.navigateBack = function(toStoryElement,fromStoryElement,historyInfo) {
// Get the animation duration
var d = ($tw.config.preferences.animationDuration/1000).toFixed(8) + "s";
var d = $tw.config.preferences.animationDuration + "ms";
// Set up the tiddler that is being closed
storyElementNode.domNode.style.position = "absolute";
storyElementNode.domNode.style.display = "block";
storyElementNode.domNode.style[$tw.browser.transformorigin] = "50% 50%";
storyElementNode.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(1)";
storyElementNode.domNode.style[$tw.browser.transition] = "none";
storyElementNode.domNode.style.zIndex = "0";
// Set up the tiddler we're moving back in
if(storyElement !== undefined) {
storyElement.domNode.style.position = "absolute";
storyElement.domNode.style.display = "block";
storyElement.domNode.style[$tw.browser.transformorigin] = "50% 50%";
storyElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(10)";
storyElement.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
storyElement.domNode.style.opacity = "0.0";
storyElement.domNode.style.zIndex = "500";
// Push the tiddler we're moving back to back on the stack
this.prevTiddlers.push(storyElement.children[0].params.target);
this.currTiddler = storyElement;
} else {
this.currTiddler = null;
if(fromStoryElement) {
fromStoryElement.domNode.style.position = "absolute";
fromStoryElement.domNode.style.display = "block";
fromStoryElement.domNode.style[$tw.browser.transformorigin] = "50% 50%";
fromStoryElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(1)";
fromStoryElement.domNode.style[$tw.browser.transition] = "none";
fromStoryElement.domNode.style.zIndex = "0";
}
// Set up the tiddler we're moving back in
toStoryElement.domNode.style.position = "absolute";
toStoryElement.domNode.style.display = "block";
toStoryElement.domNode.style[$tw.browser.transformorigin] = "50% 50%";
toStoryElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(10)";
toStoryElement.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
toStoryElement.domNode.style.opacity = "0.0";
toStoryElement.domNode.style.zIndex = "500";
this.currentTiddler = toStoryElement;
// Animate them both
$tw.utils.nextTick(function() {
// First, the tiddler we're closing
storyElementNode.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
storyElementNode.domNode.style.opacity = "0.0";
storyElementNode.domNode.style[$tw.browser.transformorigin] = "50% 50%";
storyElementNode.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(0.1)";
storyElementNode.domNode.style.zIndex = "0";
storyElementNode.domNode.addEventListener($tw.browser.transitionEnd,function(event) {
// Delete the DOM node when the transition is over
if(storyElementNode.domNode.parentNode) {
storyElementNode.domNode.parentNode.removeChild(storyElementNode.domNode);
}
},true);
// Now the tiddler we're going back to
if(storyElement !== undefined) {
storyElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(1)";
storyElement.domNode.style.opacity = "1.0";
if(fromStoryElement) {
fromStoryElement.domNode.style[$tw.browser.transition] = "-" + $tw.browser.prefix.toLowerCase() + "-transform " + d + " ease-in-out, opacity " + d + " ease-out";
fromStoryElement.domNode.style.opacity = "0.0";
fromStoryElement.domNode.style[$tw.browser.transformorigin] = "50% 50%";
fromStoryElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(0.1)";
fromStoryElement.domNode.style.zIndex = "0";
var eventHandler = function(event) {
// Delete the DOM node when the transition is over
if(fromStoryElement.domNode.parentNode) {
fromStoryElement.domNode.parentNode.removeChild(fromStoryElement.domNode);
}
fromStoryElement.domNode.removeEventListener($tw.browser.transitionEnd,eventHandler,true);
};
fromStoryElement.domNode.addEventListener($tw.browser.transitionEnd,eventHandler,true);
}
// Now the tiddler we're going back to
toStoryElement.domNode.style[$tw.browser.transform] = "translateX(0px) translateY(0px) scale(1)";
toStoryElement.domNode.style.opacity = "1.0";
});
return true; // Indicate that we'll delete the DOM node
};

59
core/modules/scroller.js Normal file
View File

@ -0,0 +1,59 @@
/*\
title: $:/core/modules/scroller.js
type: application/javascript
module-type: utils
Plugin that creates a $tw.utils.Scroller object prototype that manages scrolling in the browser
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var slowInSlowOut = function(t) {
return (1 - ((Math.cos(t * Math.PI) + 1) / 2));
};
/*
Creates a Scroller object
*/
var Scroller = function() {
};
Scroller.prototype.cancel = function() {
if(this.timerId) {
window.clearInterval(this.timerId);
this.timerId = null;
}
};
/*
Smoothly scroll an element back into view if needed
*/
Scroller.prototype.scrollIntoView = function(element) {
var scrollPosition = $tw.utils.getScrollPosition();
this.cancel();
this.startTime = new Date();
this.startX = scrollPosition.x;
this.startY = scrollPosition.y;
this.endX = element.offsetLeft;
this.endY = element.offsetTop;
if((this.endX < this.startX) || (this.endX > (this.startX + window.innerWidth)) || (this.endY < this.startY) || (this.endY > (this.startY + window.innerHeight))) {
var self = this;
this.timerId = window.setInterval(function() {
var t = ((new Date()) - self.startTime) / $tw.config.preferences.animationDuration;
if(t >= 1) {
self.cancel();
t = 1;
}
t = slowInSlowOut(t);
window.scrollTo(self.startX + (self.endX - self.startX) * t,self.startY + (self.endY - self.startY) * t);
}, 10);
}
};
exports.Scroller = Scroller;
})();

View File

@ -76,34 +76,18 @@ exports.applyStyleSheet = function(id,css) {
};
/*
Smoothly scroll an element back into view if needed
Get the scroll position of the viewport
Returns:
{
x: horizontal scroll position in pixels,
y: vertical scroll position in pixels
}
*/
exports.scrollIntoView = function(element) {
var slowInSlowOut = function(t) {
return (1 - ((Math.cos(t * Math.PI) + 1) / 2));
},
animateScroll = function(startX,startY,endX,endY,duration) {
var startTime = new Date(),
timerId = window.setInterval(function() {
var t = ((new Date()) - startTime) / duration;
if(t >= 1) {
window.clearInterval(timerId);
t = 1;
}
t = slowInSlowOut(t);
var x = startX + (endX - startX) * t,
y = startY + (endY - startY) * t;
window.scrollTo(x,y);
}, 10);
},
x = element.offsetLeft,
y = element.offsetTop,
winWidth = window.innerWidth,
winHeight = window.innerHeight,
scrollLeft = window.scrollX || document.documentElement.scrollLeft,
scrollTop = window.scrollY || document.documentElement.scrollTop;
if((x < scrollLeft) || (x > (scrollLeft + winWidth)) || (y < scrollTop) || (y > (scrollTop + winHeight))) {
animateScroll(scrollLeft,scrollTop,x,y,$tw.config.preferences.animationDuration);
exports.getScrollPosition = function() {
if("scrollX" in window) {
return {x: window.scrollX, y: window.scrollY};
} else {
return {x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop};
}
};