Attribute view support sticky layout (#9617)

* 🎨 Frozen the first col of the attribute view

* 🎨 Optimized the add row

* 🎨 Fixed the style of the first and last rows to misalign in attribute view

the height of the breadcrumbs caused the first and last rows to misalign in attribute view

* 🎨 Improve attribute view row gutter

* Update _av.scss
This commit is contained in:
Yingyi / 颖逸 2023-11-10 08:41:47 +08:00 committed by GitHub
parent 4190c7bf8d
commit 433f4d5541
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 372 additions and 80 deletions

View file

@ -4,6 +4,7 @@ import {fetchPost} from "../../util/fetch";
import {onGet} from "../util/onGet";
import {isMobile} from "../../util/functions";
import {hasClosestBlock, hasClosestByClassName} from "../util/hasClosest";
import {stickyScrollY} from "./stickyScroll";
let getIndexTimeout: number;
export const scrollEvent = (protyle: IProtyle, element: HTMLElement) => {
@ -25,26 +26,18 @@ export const scrollEvent = (protyle: IProtyle, element: HTMLElement) => {
}
protyle.wysiwyg.element.querySelectorAll(".av").forEach((item: HTMLElement) => {
if (item.parentElement.classList.contains("protyle-wysiwyg")) {
const headerTop = item.offsetTop + 43;
const headerElement = item.querySelector(".av__row--header") as HTMLElement;
if (headerElement) {
if (headerTop < element.scrollTop && headerTop + headerElement.parentElement.clientHeight > element.scrollTop) {
headerElement.style.transform = `translateY(${element.scrollTop - headerTop}px)`;
} else {
headerElement.style.transform = "";
}
}
const footerElement = item.querySelector(".av__row--footer") as HTMLElement;
if (footerElement) {
const footerBottom = headerTop + footerElement.parentElement.clientHeight;
const scrollBottom = element.scrollTop + element.clientHeight + 5;
if (headerTop + 42 + 36 * 2 < scrollBottom && footerBottom > scrollBottom) {
footerElement.style.transform = `translateY(${scrollBottom - footerBottom}px)`;
} else {
footerElement.style.transform = "";
}
}
const bodyElement = item.querySelector(".av__body") as HTMLElement;
if (bodyElement) {
const headerElement = bodyElement.querySelector(".av__row--header") as HTMLElement;
const footerElement = bodyElement.querySelector(".av__row--footer") as HTMLElement;
stickyScrollY(
element,
bodyElement,
headerElement ? [{element: headerElement}] : [],
footerElement ? [{element: footerElement}] : [],
);
}
});

View file

@ -0,0 +1,276 @@
export interface IStickyPositionY {
top: number;
bottom: number;
}
export interface IStickyElementY {
element: HTMLElement;
offset?: number;
}
export interface IStickyContextY extends IStickyElementY {
rect: DOMRect;
base: number;
origin: IStickyPositionY;
current: IStickyPositionY;
target: IStickyPositionY;
style: IStickyPositionY;
}
export const stickyScrollY = (
view: HTMLElement, // 视口元素
container: HTMLElement, // 容器元素
topElements: IStickyElementY[] = [], // 顶部粘性元素
bottomElements: IStickyElementY[] = [], // 底部粘性元素
) => {
if (topElements.length === 0 && bottomElements.length === 0) {
return;
}
const viewRect = view.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
/**
* ---------------
* | view |
* --------------- viewRect.bottom
* ----------- -- containerRect.top
* | container |
* -----------
* ====== OR ======
* -----------
* | container |
* ----------- -- containerRect.bottom
* --------------- viewRect.top
* | view |
* ---------------
*/
if (viewRect.bottom <= containerRect.top || containerRect.bottom <= viewRect.top) {
return;
}
const topContext: IStickyContextY[] = topElements.map(item => {
const rect = item.element.getBoundingClientRect();
const base = Number.parseFloat(item.element.style.top) || 0;
item.offset ??= 0;
return {
...item,
rect,
base,
origin: {
top: rect.top - base,
bottom: rect.bottom - base,
},
current: {
top: rect.top,
bottom: rect.bottom,
},
target: {
top: null,
bottom: null,
},
style: {
top: null,
bottom: null,
}
};
});
const bottomContext: IStickyContextY[] = bottomElements.map(item => {
const rect = item.element.getBoundingClientRect();
const base = Number.parseFloat(item.element.style.bottom) || 0;
item.offset ??= 0;
return {
...item,
rect,
base,
origin: {
top: rect.top + base,
bottom: rect.bottom + base,
},
current: {
top: rect.top,
bottom: rect.bottom,
},
target: {
top: null,
bottom: null,
},
style: {
top: null,
bottom: null,
}
};
});
let handleTop = false;
let handleBottom = false;
switch (true) {
/**
* --------------- viewRect.top
* | |
* | view |
* | ----------- | containerRect.top
* ------------- viewRect.bottom
* | container |
* ----------- -- containerRect.bottom
*/
case viewRect.top <= containerRect.top
&& containerRect.top <= viewRect.bottom
&& viewRect.top <= viewRect.bottom:
handleBottom = true;
break;
/**
* --------------- viewRect.top
* | view |
* | ----------- | containerRect.top
* | | container | |
* | ----------- | containerRect.bottom
* --------------- viewRect.bottom
*/
case viewRect.top <= containerRect.top
&& containerRect.bottom <= viewRect.bottom:
break;
/**
* ----------- -- containerRect.top
* ------------- viewRect.top
* | | | |} view
* ------------- viewRect.bottom
* | container |
* ----------- -- containerRect.bottom
*/
case containerRect.top <= viewRect.top
&& viewRect.bottom <= containerRect.bottom:
handleTop = true;
handleBottom = true;
break;
/**
* ----------- -- containerRect.top
* | container |
* ------------- viewRect.top
* | ----------- | containerRect.bottom
* | view |
* -|-----------|- viewRect.bottom
*/
case containerRect.top <= viewRect.top
&& viewRect.top <= containerRect.bottom
&& containerRect.bottom <= viewRect.bottom:
handleTop = true;
break;
default:
break;
}
if (handleTop) {
if (topContext.length > 0) {
topContext.reduceRight((next, current) => {
switch (true) {
/**
* ----------- -- containerRect.top
* ------------- viewRect.top
* | | | | current.target.top - current.offset
* | | | | current.origin.top
* | | | | current.origin.bottom
* | | | | next.origin.top
* | | | | next.origin.bottom
* ------------- viewRect.bottom
* ----------- -- containerRect.bottom
*/
case viewRect.top <= (current.origin.top - current.offset):
current.target.top = current.origin.top;
current.target.bottom = current.origin.bottom;
current.style.top = null;
break;
/**
* ----------- -- containerRect.top
* | | -- current.origin.top
* | | -- current.origin.bottom
* ------------- viewRect.top
* | | ------- | | current.target.top
* | | ------- | | current.target.bottom
* ------------- viewRect.bottom
* | container |
* ----------- -- containerRect.bottom
*/
default:
current.target.top = viewRect.top + current.offset;
current.target.bottom = current.target.top + current.rect.height;
if (next) {
const nextTop = Math.min(next.target.top, next.origin.top);
if (nextTop < current.target.bottom) {
const diff = nextTop - current.target.bottom;
current.target.top += diff;
current.target.bottom += diff;
}
}
current.style.top = current.base + (current.target.top - current.current.top);
break;
}
return current;
}, null);
}
}
if (handleBottom) {
if (bottomContext.length > 0) {
bottomContext.reduce((last, current) => {
switch (true) {
/**
* ----------- -- containerRect.top
* ------------- viewRect.top
* | | | | last.origin.top
* | | | | last.origin.bottom
* | | | | current.origin.top
* | | | | current.origin.bottom
* | | | | current.target.bottom + current.offset
* ------------- viewRect.bottom
* ----------- -- containerRect.bottom
*/
case (current.origin.bottom + current.offset) <= viewRect.bottom:
current.target.top = current.origin.top;
current.target.bottom = current.origin.bottom;
current.style.bottom = null;
break;
/**
* ----------- -- containerRect.top
* ------------- viewRect.top
* | | ------- | | current.target.top
* | | ------- | | current.target.bottom
* ------------- viewRect.bottom
* | | -- current.origin.top
* | | -- current.origin.bottom
* | container |
* ----------- -- containerRect.bottom
*/
default:
current.target.bottom = viewRect.bottom - current.offset;
current.target.top = current.target.bottom - current.rect.height;
if (last) {
const lastBottom = Math.max(last.target.bottom, last.origin.bottom);
if (current.target.top < lastBottom) {
const diff = lastBottom - current.target.top;
current.target.top += diff;
current.target.bottom += diff;
}
}
current.style.bottom = current.base - (current.target.bottom - current.current.bottom);
break;
}
return current;
}, null);
}
}
[...topContext, ...bottomContext].forEach(item => {
item.element.style.top = item.style.top ? `${item.style.top}px` : null;
item.element.style.bottom = item.style.bottom ? `${item.style.bottom}px` : null;
});
}