Merge pull request '[PR]: Bring back the scrollable sticky widgets sidebar' (#10294) from Freeplay/calckey:sticky into develop
Reviewed-on: https://codeberg.org/calckey/calckey/pulls/10294
This commit is contained in:
commit
5ebb0e1c8d
|
@ -2,79 +2,56 @@ export class StickySidebar {
|
||||||
private lastScrollTop = 0;
|
private lastScrollTop = 0;
|
||||||
private container: HTMLElement;
|
private container: HTMLElement;
|
||||||
private el: HTMLElement;
|
private el: HTMLElement;
|
||||||
private spacer: HTMLElement;
|
|
||||||
private marginTop: number;
|
|
||||||
private isTop = false;
|
private isTop = false;
|
||||||
private isBottom = false;
|
private isBottom = false;
|
||||||
private offsetTop: number;
|
private offsetTop: number;
|
||||||
private globalHeaderHeight = 59;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: StickySidebar["container"],
|
container: StickySidebar["container"],
|
||||||
marginTop = 0,
|
|
||||||
globalHeaderHeight = 0,
|
|
||||||
) {
|
) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
this.container.style.display = "flex";
|
||||||
this.el = this.container.children[0] as HTMLElement;
|
this.el = this.container.children[0] as HTMLElement;
|
||||||
this.el.style.position = "sticky";
|
this.el.style.position = "sticky";
|
||||||
this.spacer = document.createElement("div");
|
|
||||||
this.container.prepend(this.spacer);
|
|
||||||
this.marginTop = marginTop;
|
|
||||||
this.offsetTop = this.container.getBoundingClientRect().top;
|
this.offsetTop = this.container.getBoundingClientRect().top;
|
||||||
this.globalHeaderHeight = globalHeaderHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public calc(scrollTop: number) {
|
public calc(scrollTop: number) {
|
||||||
if (scrollTop > this.lastScrollTop) {
|
if (scrollTop > this.lastScrollTop) {
|
||||||
// downscroll
|
// downscroll
|
||||||
const overflow = Math.max(
|
|
||||||
0,
|
|
||||||
this.globalHeaderHeight +
|
|
||||||
(this.el.clientHeight + this.marginTop) -
|
|
||||||
window.innerHeight,
|
|
||||||
);
|
|
||||||
this.el.style.bottom = null;
|
|
||||||
this.el.style.top = `${
|
|
||||||
-overflow + this.marginTop + this.globalHeaderHeight
|
|
||||||
}px`;
|
|
||||||
|
|
||||||
this.isBottom =
|
|
||||||
scrollTop + window.innerHeight >=
|
|
||||||
this.el.offsetTop + this.el.clientHeight;
|
|
||||||
|
|
||||||
if (this.isTop) {
|
|
||||||
this.isTop = false;
|
this.isTop = false;
|
||||||
this.spacer.style.marginTop = `${Math.max(
|
this.isBottom =
|
||||||
0,
|
scrollTop + window.innerHeight >
|
||||||
this.globalHeaderHeight +
|
this.el.offsetTop + this.el.clientHeight;
|
||||||
this.lastScrollTop +
|
|
||||||
this.marginTop -
|
|
||||||
this.offsetTop,
|
|
||||||
)}px`;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// upscroll
|
// upscroll
|
||||||
const overflow =
|
|
||||||
this.globalHeaderHeight +
|
|
||||||
(this.el.clientHeight + this.marginTop) -
|
|
||||||
window.innerHeight;
|
|
||||||
this.el.style.top = null;
|
|
||||||
this.el.style.bottom = `${-overflow}px`;
|
|
||||||
|
|
||||||
this.isTop =
|
|
||||||
scrollTop + this.marginTop + this.globalHeaderHeight <=
|
|
||||||
this.el.offsetTop;
|
|
||||||
|
|
||||||
if (this.isBottom) {
|
|
||||||
this.isBottom = false;
|
this.isBottom = false;
|
||||||
this.spacer.style.marginTop = `${
|
this.isTop = scrollTop < this.el.offsetTop + 1;
|
||||||
this.globalHeaderHeight +
|
|
||||||
this.lastScrollTop +
|
|
||||||
this.marginTop -
|
|
||||||
this.offsetTop -
|
|
||||||
overflow
|
|
||||||
}px`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isTop) {
|
||||||
|
if (this.el.style.alignSelf != "flex-start") {
|
||||||
|
this.el.style.position = "sticky";
|
||||||
|
this.el.style.bottom = null;
|
||||||
|
this.el.style.top = "0px";
|
||||||
|
this.el.style.alignSelf = "flex-start";
|
||||||
|
console.log("top");
|
||||||
|
}
|
||||||
|
this.offsetTop = scrollTop;
|
||||||
|
} else if (this.isBottom) {
|
||||||
|
if (this.el.style.alignSelf != "flex-end") {
|
||||||
|
this.el.style.position = "sticky";
|
||||||
|
this.el.style.bottom = "0px";
|
||||||
|
this.el.style.top = null;
|
||||||
|
this.el.style.alignSelf = "flex-end";
|
||||||
|
console.log("bottom");
|
||||||
|
}
|
||||||
|
this.offsetTop = window.innerHeight + scrollTop - this.el.scrollHeight;
|
||||||
|
} else {
|
||||||
|
this.el.style.position = "relative";
|
||||||
|
this.el.style.top = this.offsetTop + "px";
|
||||||
|
this.el.style.bottom = null;
|
||||||
|
this.el.style.alignSelf = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
|
this.lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
|
|
||||||
<div v-if="isDesktop" ref="widgetsEl" class="widgets-container">
|
<div v-if="isDesktop" ref="widgetsEl" class="widgets-container">
|
||||||
<XWidgets />
|
<XWidgets @mounted="attachSticky" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -380,6 +380,20 @@ const onContextmenu = (ev: MouseEvent) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const attachSticky = (el: any) => {
|
||||||
|
let lastScrollTop = 0;
|
||||||
|
addEventListener(
|
||||||
|
"scroll",
|
||||||
|
(ev) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
widgetsEl.scrollTop += window.scrollY - lastScrollTop;
|
||||||
|
lastScrollTop = window.scrollY <= 0 ? 0 : window.scrollY;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function top() {
|
function top() {
|
||||||
window.scroll({ top: 0, behavior: "smooth" });
|
window.scroll({ top: 0, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
@ -440,7 +454,7 @@ console.log(mainRouter.currentRoute.value.name);
|
||||||
$widgets-hide-threshold: 1090px;
|
$widgets-hide-threshold: 1090px;
|
||||||
|
|
||||||
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||||
height: 100%;
|
min-height: calc(var(--vh, 1vh) * 100);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
@ -550,13 +564,17 @@ console.log(mainRouter.currentRoute.value.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .widgets-container {
|
> .widgets-container {
|
||||||
margin-right: calc(var(--margin) / 2);
|
position: sticky;
|
||||||
width: calc(300px + (var(--margin) * 1.5));
|
top: 0;
|
||||||
flex: 0 0 auto;
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 var(--margin);
|
||||||
|
width: 300px;
|
||||||
|
min-width: max-content;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
> :deep(.widgets) {
|
scrollbar-width: none;
|
||||||
padding-left: var(--margin);
|
&::-webkit-scrollbar {
|
||||||
padding-right: calc(var(--margin) / 2);
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $widgets-hide-threshold) {
|
@media (max-width: $widgets-hide-threshold) {
|
||||||
|
|
|
@ -84,23 +84,18 @@ function updateWidgets(widgets) {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.widgets {
|
.widgets {
|
||||||
position: sticky;
|
|
||||||
top: var(--stickyTop, 0px);
|
|
||||||
height: min-content;
|
height: min-content;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
max-height: 100vh;
|
padding: var(--margin) 0;
|
||||||
box-sizing: content-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden auto;
|
|
||||||
&:not(:hover):not(:focus-within)::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
margin: var(--margin) 0;
|
margin: var(--margin) 0;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
> :first-child {
|
|
||||||
margin-top: calc(var(--margin) * 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .add {
|
> .add {
|
||||||
|
|
Loading…
Reference in New Issue