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">× <$link to={{!!title}}><$view field="title"/> -<$set name="state" value={{{ [[$:/state/viewtemplate/visibility/]addsuffix] }}}> -<$set name="visibility" tiddler=<>> -- <$text text=<>/> - - +<$reveal type="match" stateTitle={{{ [[$:/state/viewtemplate/visibility/]addsuffix] }}} text="true"> +LOADED +
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: <offset in pixels from the top of the tiddler>} +*/ +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 +<<if-tiddler-is title:"$:/config/DynaView/PreserveScrollPosition" value:"yes" text:""" +body { + overflow-anchor: none; /* Turn off browser scroll anchoring */ +} +""">> + 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,