mirror of
https://github.com/Jermolene/TiddlyWiki5.git
synced 2025-12-06 02:30:46 -08:00
Merge 9aec77b814 into 5cd3084298
This commit is contained in:
commit
e95f885831
2 changed files with 127 additions and 12 deletions
|
|
@ -60,6 +60,25 @@ PageScroller.prototype.handleEvent = function(event) {
|
|||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
Find the scrollable parent of an element
|
||||
*/
|
||||
PageScroller.prototype.findScrollableParent = function(element) {
|
||||
if(!element) {
|
||||
return window;
|
||||
}
|
||||
var parent = element.parentElement;
|
||||
while(parent) {
|
||||
var overflowY = window.getComputedStyle(parent).overflowY;
|
||||
var isScrollable = overflowY === "auto" || overflowY === "scroll";
|
||||
if(isScrollable && parent.scrollHeight > parent.clientHeight) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return window;
|
||||
};
|
||||
|
||||
/*
|
||||
Handle a scroll event hitting the page document
|
||||
*/
|
||||
|
|
@ -67,6 +86,9 @@ PageScroller.prototype.scrollIntoView = function(element,callback,options) {
|
|||
var self = this,
|
||||
duration = $tw.utils.hop(options,"animationDuration") ? parseInt(options.animationDuration) : $tw.utils.getAnimationDuration(),
|
||||
srcWindow = element ? element.ownerDocument.defaultView : window;
|
||||
// Find the scrollable parent
|
||||
var scrollContainer = this.findScrollableParent(element);
|
||||
var isWindowScroll = scrollContainer === window || scrollContainer === srcWindow;
|
||||
// Now get ready to scroll the body
|
||||
this.cancelScroll(srcWindow);
|
||||
this.startTime = Date.now();
|
||||
|
|
@ -76,16 +98,46 @@ PageScroller.prototype.scrollIntoView = function(element,callback,options) {
|
|||
if(toolbar) {
|
||||
offset = toolbar.offsetHeight;
|
||||
}
|
||||
// Get the scroll-margin-top and scroll-margin-left values from the element
|
||||
var scrollMarginTop = 0, scrollMarginLeft = 0;
|
||||
if(element) {
|
||||
var computedStyle = srcWindow.getComputedStyle(element);
|
||||
var marginTop = computedStyle.getPropertyValue("scroll-margin-top");
|
||||
if(marginTop) {
|
||||
scrollMarginTop = parseFloat(marginTop) || 0;
|
||||
}
|
||||
var marginLeft = computedStyle.getPropertyValue("scroll-margin-left");
|
||||
if(marginLeft) {
|
||||
scrollMarginLeft = parseFloat(marginLeft) || 0;
|
||||
}
|
||||
}
|
||||
// Get the client bounds of the element and adjust by the scroll position
|
||||
var getBounds = function() {
|
||||
var clientBounds = typeof callback === 'function' ? callback() : element.getBoundingClientRect(),
|
||||
scrollPosition;
|
||||
|
||||
if(isWindowScroll) {
|
||||
scrollPosition = $tw.utils.getScrollPosition(srcWindow);
|
||||
return {
|
||||
left: clientBounds.left + scrollPosition.x,
|
||||
top: clientBounds.top + scrollPosition.y - offset,
|
||||
left: clientBounds.left + scrollPosition.x - scrollMarginLeft,
|
||||
top: clientBounds.top + scrollPosition.y - offset - scrollMarginTop,
|
||||
width: clientBounds.width,
|
||||
height: clientBounds.height
|
||||
};
|
||||
} else {
|
||||
// For container scroll, calculate position relative to container
|
||||
var containerBounds = scrollContainer.getBoundingClientRect();
|
||||
scrollPosition = {
|
||||
x: scrollContainer.scrollLeft,
|
||||
y: scrollContainer.scrollTop
|
||||
};
|
||||
return {
|
||||
left: clientBounds.left - containerBounds.left + scrollPosition.x - scrollMarginLeft,
|
||||
top: clientBounds.top - containerBounds.top + scrollPosition.y - offset - scrollMarginTop,
|
||||
width: clientBounds.width,
|
||||
height: clientBounds.height
|
||||
};
|
||||
}
|
||||
},
|
||||
// We'll consider the horizontal and vertical scroll directions separately via this function
|
||||
// targetPos/targetSize - position and size of the target element
|
||||
|
|
@ -111,11 +163,30 @@ PageScroller.prototype.scrollIntoView = function(element,callback,options) {
|
|||
t = 1;
|
||||
}
|
||||
t = $tw.utils.slowInSlowOut(t);
|
||||
var scrollPosition = $tw.utils.getScrollPosition(srcWindow),
|
||||
bounds = getBounds(),
|
||||
endX = getEndPos(bounds.left,bounds.width,scrollPosition.x,srcWindow.innerWidth),
|
||||
endY = getEndPos(bounds.top,bounds.height,scrollPosition.y,srcWindow.innerHeight);
|
||||
var scrollPosition, bounds, endX, endY, viewportWidth, viewportHeight;
|
||||
|
||||
if(isWindowScroll) {
|
||||
scrollPosition = $tw.utils.getScrollPosition(srcWindow);
|
||||
bounds = getBounds();
|
||||
viewportWidth = srcWindow.innerWidth;
|
||||
viewportHeight = srcWindow.innerHeight;
|
||||
endX = getEndPos(bounds.left,bounds.width,scrollPosition.x,viewportWidth);
|
||||
endY = getEndPos(bounds.top,bounds.height,scrollPosition.y,viewportHeight);
|
||||
srcWindow.scrollTo(scrollPosition.x + (endX - scrollPosition.x) * t,scrollPosition.y + (endY - scrollPosition.y) * t);
|
||||
} else {
|
||||
scrollPosition = {
|
||||
x: scrollContainer.scrollLeft,
|
||||
y: scrollContainer.scrollTop
|
||||
};
|
||||
bounds = getBounds();
|
||||
viewportWidth = scrollContainer.clientWidth;
|
||||
viewportHeight = scrollContainer.clientHeight;
|
||||
endX = getEndPos(bounds.left,bounds.width,scrollPosition.x,viewportWidth);
|
||||
endY = getEndPos(bounds.top,bounds.height,scrollPosition.y,viewportHeight);
|
||||
scrollContainer.scrollLeft = scrollPosition.x + (endX - scrollPosition.x) * t;
|
||||
scrollContainer.scrollTop = scrollPosition.y + (endY - scrollPosition.y) * t;
|
||||
}
|
||||
|
||||
if(t < 1) {
|
||||
self.idRequestFrame = self.requestAnimationFrame.call(srcWindow,drawFrame);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,59 @@
|
|||
caption: tm-scroll
|
||||
created: 20160425000906330
|
||||
modified: 20201014152456174
|
||||
modified: 20250809084836027
|
||||
tags: Messages
|
||||
title: WidgetMessage: tm-scroll
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
\procedure example-content()
|
||||
\procedure openingBracket() [
|
||||
\procedure closingBracket() ]
|
||||
\procedure quote() "
|
||||
\function tf.navigate-to-css-escaped() [<navigateTo>escapecss[]]
|
||||
\procedure linkcatcher-actions()
|
||||
<$action-sendmessage $message="tm-scroll" selector={{{ [[.tc-scrollable-container-example-scroll-container ]addsuffix<openingBracket>addsuffix[data-tiddler-title=]addsuffix<quote>addsuffix<tf.navigate-to-css-escaped>addsuffix<quote>addsuffix<closingBracket>addsuffix[.tc-tiddler-frame]] }}}/>
|
||||
\end linkcatcher-actions
|
||||
|
||||
<div class="tc-scrollable-container-example" style="height: 75vh; border: 1px solid #ccc; display: flex;">
|
||||
<div style="width: 25%; overflow: auto;">
|
||||
<$linkcatcher actions=<<linkcatcher-actions>>>
|
||||
{{$:/core/ui/SideBar/Recent}}
|
||||
</$linkcatcher>
|
||||
</div>
|
||||
<div class="tc-scrollable-container-example-scroll-container" style="width: 75%; overflow: auto; padding: 1rem;">
|
||||
<$list filter="[all[tiddlers]!is[system]!sort[modified]limit{$:/config/RecentLimit}] -[<currentTiddler>]" storyview="classic" template="$:/core/ui/ViewTemplate"/>
|
||||
</div>
|
||||
</div>
|
||||
\end example-content
|
||||
|
||||
The `tm-scroll` message causes the surrounding scrollable container to scroll to the specified DOM node into view. The `tm-scroll` is handled in various places in the core itself, but can also be handled by a [[ScrollableWidget]].
|
||||
|
||||
The core scroller implementation (`$:/core/modules/utils/dom/scroller.js`) provides intelligent scrolling behavior that:
|
||||
|
||||
* Automatically detects the nearest scrollable parent container (elements with `overflow: auto` or `overflow: scroll`)
|
||||
* Falls back to window scrolling if no scrollable parent is found
|
||||
* Respects CSS `scroll-margin-top` property on target elements for proper spacing
|
||||
* Accounts for fixed toolbars with class `tc-adjust-top-of-scroll` to prevent content being hidden
|
||||
* Uses smooth animation with configurable duration via the `slowInSlowOut` easing function
|
||||
* Supports both window-level and container-level scrolling with proper coordinate calculations
|
||||
* Snaps to top/left edges when scrolling within 50 pixels of them for cleaner positioning
|
||||
|
||||
|!Name |!Description |
|
||||
|target |Target DOM node the scrollable container should scroll to (note that this parameter can only be set via JavaScript code) |
|
||||
|selector |<<.from-version "5.1.23">> Optional string [[CSS selector|https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors]] as an alternate means of identifying the target DOM node |
|
||||
|animationDuration |<<.from-version "5.2.2">> Optional number specifying the animation duration in milliseconds for the scrolling. Defaults to the [[global animation duration|$:/config/AnimationDuration]]. |
|
||||
|
||||
<<.tip "Set `animationDuration` to `0` to scroll without animation">>
|
||||
|
||||
!! Implementation Details
|
||||
|
||||
The scroller uses `requestAnimationFrame` for smooth scrolling performance and provides methods to:
|
||||
|
||||
* Check if scrolling is currently in progress via `isScrolling()`
|
||||
* Cancel an in-progress scroll animation via `cancelScroll()`
|
||||
* Handle both direct element references and CSS selector-based targeting
|
||||
|
||||
Here's an example how <<.from-version "5.4.0">> the `tm-scroll` message can be used to scroll any scrollable container<br>
|
||||
When clicking any of the links within the left pane you can observe that the container at the right scrolls to the navigated element
|
||||
|
||||
<$transclude $variable=".example" n="1" eg=<<example-content>>/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue