diff --git a/core/modules/startup/render.js b/core/modules/startup/render.js
index 4e7d7c828..fe99ed21a 100644
--- a/core/modules/startup/render.js
+++ b/core/modules/startup/render.js
@@ -71,9 +71,10 @@ exports.startup = function() {
timerId;
function refresh() {
// Process the refresh
+ $tw.hooks.invokeHook("th-page-refreshing");
$tw.pageWidgetNode.refresh(deferredChanges);
deferredChanges = Object.create(null);
- $tw.hooks.invokeHook("th-page-refreshed");
+ $tw.hooks.invokeHook("th-page-refreshed");
}
// Add the change event handler
$tw.wiki.addEventListener("change",$tw.perf.report("mainRefresh",function(changes) {
diff --git a/core/modules/utils/dom/scroller.js b/core/modules/utils/dom/scroller.js
index 8d64b2b2f..167067add 100644
--- a/core/modules/utils/dom/scroller.js
+++ b/core/modules/utils/dom/scroller.js
@@ -33,6 +33,10 @@ var PageScroller = function() {
};
};
+PageScroller.prototype.isScrolling = function() {
+ return this.idRequestFrame !== null;
+}
+
PageScroller.prototype.cancelScroll = function(srcWindow) {
if(this.idRequestFrame) {
this.cancelAnimationFrame.call(srcWindow,this.idRequestFrame);
diff --git a/editions/dev/tiddlers/new/Hook_ th-page-refreshing.tid b/editions/dev/tiddlers/new/Hook_ th-page-refreshing.tid
new file mode 100644
index 000000000..e3343e098
--- /dev/null
+++ b/editions/dev/tiddlers/new/Hook_ th-page-refreshing.tid
@@ -0,0 +1,15 @@
+created: 20190111150102847
+modified: 20190111150102847
+tags: HookMechanism
+title: Hook: th-page-refreshing
+type: text/vnd.tiddlywiki
+
+This hook notifies plugins that a page refresh is just about to occur. It is typically used to apply pre-rendering effects.
+
+Hook function parameters:
+
+* (none)
+
+Return value:
+
+* (none)
diff --git a/editions/dynaviewdemo/tiddlers/AnimationDuration.tid b/editions/dynaviewdemo/tiddlers/AnimationDuration.tid
new file mode 100644
index 000000000..bba70f900
--- /dev/null
+++ b/editions/dynaviewdemo/tiddlers/AnimationDuration.tid
@@ -0,0 +1,2 @@
+title: $:/config/AnimationDuration
+text: 0
diff --git a/editions/dynaviewdemo/tiddlers/PerformanceInstrumentation.tid b/editions/dynaviewdemo/tiddlers/PerformanceInstrumentation.tid
index e4220f287..ccae6842e 100644
--- a/editions/dynaviewdemo/tiddlers/PerformanceInstrumentation.tid
+++ b/editions/dynaviewdemo/tiddlers/PerformanceInstrumentation.tid
@@ -1,2 +1,2 @@
title: $:/config/Performance/Instrumentation
-text: yes
+text: no
diff --git a/editions/dynaviewdemo/tiddlers/PreserveScrollPosition.tid b/editions/dynaviewdemo/tiddlers/PreserveScrollPosition.tid
new file mode 100644
index 000000000..c62b34478
--- /dev/null
+++ b/editions/dynaviewdemo/tiddlers/PreserveScrollPosition.tid
@@ -0,0 +1,2 @@
+title: $:/config/DynaView/PreserveScrollPosition
+text: yes
diff --git a/editions/dynaviewdemo/tiddlers/SideBar-Open.tid b/editions/dynaviewdemo/tiddlers/SideBar-Open.tid
index e3a7c9f8e..756cbdf5b 100644
--- a/editions/dynaviewdemo/tiddlers/SideBar-Open.tid
+++ b/editions/dynaviewdemo/tiddlers/SideBar-Open.tid
@@ -17,11 +17,9 @@ caption: {{$:/language/SideBar/Open/Caption}}
<$button message="tm-close-tiddler" tooltip={{$:/language/Buttons/Close/Hint}} aria-label={{$:/language/Buttons/Close/Caption}} class="tc-btn-invisible tc-btn-mini">×$button> <$link to={{!!title}}><$view field="title"/>$link>
-<$set name="state" value={{{ [[$:/state/viewtemplate/visibility/]addsuffix] }}}>
-<$set name="visibility" tiddler=<>>
-- <$text text=<>/>
-$set>
-$set>
+<$reveal type="match" stateTitle={{{ [[$:/state/viewtemplate/visibility/]addsuffix] }}} text="true">
+LOADED
+$reveal>
$droppable>
diff --git a/plugins/tiddlywiki/dynaview/docs.tid b/plugins/tiddlywiki/dynaview/docs.tid
index e40fc33b3..f9492254d 100644
--- a/plugins/tiddlywiki/dynaview/docs.tid
+++ b/plugins/tiddlywiki/dynaview/docs.tid
@@ -13,6 +13,12 @@ The components of this plugin include:
! Scroll Features
+!! Scroll Position Preservation
+
+Some recent browsers have a feature called "scroll anchoring" whereby they suppress the apparent scrolling that occurs when elements are inserted or removed above the current viewport. (See https://github.com/WICG/ScrollAnchoring for more details).
+
+~DynaView can optionally polyfill this behaviour for older browsers by setting the configuration tiddler $:/config/DynaView/PreserveScrollPosition to `yes`.
+
!! Set tiddler field when visible
The background task detects when elements with the class `tc-dynaview-set-tiddler-when-visible` scroll into view. The first time that they do, the background task assigns the value in the attribute `data-dynaview-set-value` to the tiddler whose title is in the attribute `data-dynaview-set-tiddler`. This assignment can be tied to a reveal widget to cause content to be displayed when it becomes visible. If the class `tc-dynaview-expand-viewport` is set then the viewport is expanded so that the processing occurs when elements move near the viewport.
diff --git a/plugins/tiddlywiki/dynaview/dynaview.js b/plugins/tiddlywiki/dynaview/dynaview.js
index ac9e5035a..ae67de024 100644
--- a/plugins/tiddlywiki/dynaview/dynaview.js
+++ b/plugins/tiddlywiki/dynaview/dynaview.js
@@ -24,11 +24,21 @@ var isWaitingForAnimationFrame = 0, // Bitmask:
ANIM_FRAME_CAUSED_BY_RESIZE = 4; // Animation frame was requested because of window resize
exports.startup = function() {
+ var topmost = null, lastScrollY;
window.addEventListener("load",onLoad,false);
window.addEventListener("scroll",onScroll,false);
window.addEventListener("resize",onResize,false);
+ $tw.hooks.addHook("th-page-refreshing",function() {
+ if(shouldPreserveScrollPosition()) {
+ topmost = findTopmostTiddler();
+ }
+ lastScrollY = window.scrollY;
+ });
$tw.hooks.addHook("th-page-refreshed",function() {
- checkTopmost();
+ if(lastScrollY === window.scrollY) { // Don't do scroll anchoring if the scroll position got changed
+ scrollToTiddler(topmost);
+ }
+ updateAddressBar();
checkVisibility();
saveViewportDimensions();
});
@@ -60,7 +70,7 @@ function worker() {
saveViewportDimensions();
}
setZoomClasses();
- checkTopmost();
+ updateAddressBar();
checkVisibility();
isWaitingForAnimationFrame = 0;
}
@@ -134,22 +144,11 @@ function checkVisibility() {
});
}
-function checkTopmost() {
+function updateAddressBar() {
if($tw.wiki.getTiddlerText("$:/config/DynaView/UpdateAddressBar") === "yes") {
- var elements = document.querySelectorAll(".tc-tiddler-frame[data-tiddler-title]"),
- topmostElement = null,
- topmostElementTop = 1 * 1000 * 1000;
- $tw.utils.each(elements,function(element) {
- // Check if the element is visible
- var elementRect = element.getBoundingClientRect();
- if((elementRect.top < topmostElementTop) && (elementRect.bottom > 0)) {
- topmostElement = element;
- topmostElementTop = elementRect.top;
- }
- });
- if(topmostElement) {
- var title = topmostElement.getAttribute("data-tiddler-title"),
- hash = "#" + encodeURIComponent(title) + ":" + encodeURIComponent("[list[$:/StoryList]]");
+ var top = findTopmostTiddler();
+ if(top.element) {
+ var hash = "#" + encodeURIComponent(top.title) + ":" + encodeURIComponent("[list[$:/StoryList]]");
if(title && $tw.locationHash !== hash) {
$tw.locationHash = hash;
window.location.hash = hash;
@@ -158,6 +157,51 @@ function checkTopmost() {
}
}
+/*
+tiddlerDetails: {title: , offset: }
+*/
+function scrollToTiddler(tiddlerDetails) {
+ if(shouldPreserveScrollPosition() && !$tw.pageScroller.isScrolling() && tiddlerDetails) {
+ var elements = document.querySelectorAll(".tc-tiddler-frame[data-tiddler-title]"),
+ topmostTiddlerElement = null;
+ $tw.utils.each(elements,function(element) {
+ if(element.getAttribute("data-tiddler-title") === tiddlerDetails.title) {
+ topmostTiddlerElement = element;
+ }
+ });
+ if(topmostTiddlerElement) {
+ var rect = topmostTiddlerElement.getBoundingClientRect(),
+ scrollY = Math.round(window.scrollY + rect.top + tiddlerDetails.offset);
+ if(scrollY !== window.scrollY) {
+ window.scrollTo(window.scrollX,scrollY);
+ }
+ }
+ }
+}
+
+function shouldPreserveScrollPosition() {
+ return $tw.wiki.getTiddlerText("$:/config/DynaView/PreserveScrollPosition") === "yes";
+}
+
+function findTopmostTiddler() {
+ var elements = document.querySelectorAll(".tc-tiddler-frame[data-tiddler-title]"),
+ topmostElement = null,
+ topmostElementTop = 1 * 1000 * 1000;
+ $tw.utils.each(elements,function(element) {
+ // Check if the element is visible
+ var elementRect = element.getBoundingClientRect();
+ if((elementRect.top < topmostElementTop) && (elementRect.bottom > 0)) {
+ topmostElement = element;
+ topmostElementTop = elementRect.top;
+ }
+ });
+ return {
+ element: topmostElement,
+ offset: -topmostElementTop,
+ title: topmostElement.getAttribute("data-tiddler-title")
+ };
+}
+
var previousViewportWidth, previousViewportHeight;
function saveViewportDimensions() {
diff --git a/plugins/tiddlywiki/dynaview/styles.tid b/plugins/tiddlywiki/dynaview/styles.tid
index b872f2e26..b6282a445 100644
--- a/plugins/tiddlywiki/dynaview/styles.tid
+++ b/plugins/tiddlywiki/dynaview/styles.tid
@@ -1,8 +1,19 @@
title: $:/plugins/tiddlywiki/dynaview/styles
tags: $:/tags/Stylesheet
+\define if-tiddler-is(title,value,text)
+<$reveal stateTitle=<<__title__>> text=<<__value__>> type="match">
+<$text text=<<__text__>>/>
+$reveal>
+\end
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline
+<>
+
body.tc-dynaview .tc-dynaview-zoom-visible-1-and-above,
body.tc-dynaview .tc-dynaview-zoom-visible-1a-and-above,
body.tc-dynaview .tc-dynaview-zoom-visible-1b-and-above,