Javascript 是否有可靠且性能良好的定位策略来对齐下拉组件?
我正在React/Preact中设计一个下拉组件。下拉列表的交互如下所示: 由于组件可以位于页面上的任何位置,并且任何祖先DOM元素都可以创建新的堆栈上下文或使用Javascript 是否有可靠且性能良好的定位策略来对齐下拉组件?,javascript,css,reactjs,rxjs,preact,Javascript,Css,Reactjs,Rxjs,Preact,我正在React/Preact中设计一个下拉组件。下拉列表的交互如下所示: 由于组件可以位于页面上的任何位置,并且任何祖先DOM元素都可以创建新的堆栈上下文或使用溢出:隐藏,因此我不能使用相对父级和绝对子级定位组合 我使用门户并在打开时直接将覆盖曲面作为body标记的子对象进行渲染这里的问题-如何将覆盖菜单放置在锚元素上,使其中心相互碰撞? 必须考虑的几件事: 这是一个通用组件。覆盖菜单可以有任何内容,因此可以有任何大小。预先不知道大小。如果需要,可以将其称为InlineModal或Inlin
溢出:隐藏
,因此我不能使用相对
父级和绝对
子级定位组合
我使用门户
并在打开时直接将覆盖曲面作为body
标记的子对象进行渲染这里的问题-如何将覆盖菜单放置在锚元素上,使其中心相互碰撞?
必须考虑的几件事:
InlineModal
或InlineBox
interval
和animationFrameScheduler
(内部RAF-RequestAnimationFrame
)并连续计算固定位置点,即顶部
和左侧
onResize
和onScroll
处理程序重新定位元素。但这并没有考虑到上述因素,因为我无法知道原始锚是否由于页面上的其他操作而发生了移动主体中
<代码>固定和绝对
都可以正常工作
我正在用Rx.js和Popmotion尝试的东西:
import { action } from 'popmotion';
import { Observable, interval, animationFrameScheduler } from 'rxjs';
import { map, distinctUntilChanged } from 'rxjs/operators';
export type Coordinates = readonly [number, number];
export function stickToCenterAction(reference: HTMLElement, popper: HTMLElement) {
return action(({ update }) => {
const sub = stickToCenter(reference, popper)
.subscribe(([x, y]) => update({ x, y }));
return {
stop: () => {
sub.unsubscribe();
}
};
});
}
function stickToCenter(reference: HTMLElement, popper: HTMLElement): Observable<Coordinates> {
return interval(0, animationFrameScheduler).pipe(
map(() => calculateCenter(reference, popper)),
distinctUntilChanged(([oX, oY], [nX, nY]) => oX === nX && oY === nY ));
}
function calculateCenter(reference: HTMLElement, popper: HTMLElement): Coordinates {
const [rCenterX, rCenterY] = findCenterForElement(reference);
const [x, y] = findXYForGivenCenter(popper, [rCenterX, rCenterY]);
return [x, y];
}
function findCenterForElement(reference: HTMLElement): Coordinates {
const { left, top, width, height } = reference.getBoundingClientRect();
const rCenterX = left + (width / 2);
const rCenterY = top + (height / 2);
return [rCenterX, rCenterY]
}
function findXYForGivenCenter(elm: HTMLElement, co: Coordinates): Coordinates {
const [rCenterX, rCenterY] = co;
const { width, height } = elm.getBoundingClientRect();
const x = rCenterX - (width / 2);
const y = rCenterY - (height / 2);
return [x, y];
}
从'popmotion'导入{action};
从“rxjs”导入{Observable,interval,animationFrameScheduler};
从“rxjs/operators”导入{map,distinctUntilChanged};
导出类型坐标=只读[编号,编号];
导出函数StickToInteraction(参考:HtmleElement,popper:HtmleElement){
返回操作(({update})=>{
const sub=粘滞中心(参考,提升器)
.subscribe(([x,y])=>update({x,y}));
返回{
停止:()=>{
sub.取消订阅();
}
};
});
}
函数stickToCenter(参考:HtmleElement,popper:HtmleElement):可观察{
返回间隔(0,animationFrameScheduler).pipe(
映射(()=>calculateCenter(参考,popper)),
差异化(([oX,oY],[nX,nY])=>oX==nX&&oY==nY));
}
函数calculateCenter(参考:HtmleElement,popper:HtmleElement):坐标{
const[rCenterX,rCenterY]=findCenterForElement(参考);
常数[x,y]=findXYForGivenCenter(popper[rcenter,rcentry]);
返回[x,y];
}
函数findCenterForElement(参考:HtmleElement):坐标{
const{left,top,width,height}=reference.getBoundingClientRect();
constrcenterx=左+(宽度/2);
常数中心=顶部+(高度/2);
返回[rCenterX,rCenterY]
}
函数findXYForGivenCenter(elm:HTMLElement,co:Coordinates):坐标{
常数[rCenterX,rCenterY]=co;
const{width,height}=elm.getBoundingClientRect();
常数x=r中心-(宽度/2);
常数y=R中心-(高度/2);
返回[x,y];
}
这就是它在组件中的使用方式:
import { styler, tween, composite } from 'popmotion';
export type InlineBoxProps = {
anchor: (ref: Ref<any>) => ComponentChildren;
content: (ref: Ref<any>) => ComponentChildren;
isOpen?: boolean;
};
const scaleEffect = composite({
opacity: tween({ from: 0, to: 1, duration: 400 }),
scale: tween({ from: 0.75, to: 1, duration: 240 })
});
export function InlineBox(props: InlineBoxProps) {
// ... References for anchorElm, contentElm, etc.
useEffect(() => {
if (isOpen && anchorElm && contentElm) {
const elmStyle = styler(contentElm);
const positionEffect = stickToCenterAction(anchorElm, contentElm)
.pipe(({ x, y }: any) => ({ y, x }));
const finalEffect = composite({ boom: scaleEffect, translate: positionEffect })
.filter((x) => !!x.translate)
.pipe((({ boom, translate }: any) => ({ ...boom, ...translate })));
const anim = finalEffect
.start((v: any) => elmStyle.set(v));
return () => anim.stop();
}
}, [isOpen, anchorElm, contentElm]);
return (
<Fragment>
{anchor(anchorElmRef)}
{ isOpen
? <PortalIntoBody>{content(contentElmRef)}</PortalIntoBody>
: null }
</Fragment>
);
}
从'popmotion'导入{styler,tween,composite};
导出类型InlineBoxProps={
锚定:(ref:ref)=>组件子级;
内容:(ref:ref)=>ComponentChildren;
等参数?:布尔数;
};
常数scaleEffect=复合({
不透明度:tween({from:0,to:1,duration:400}),
比例:tween({from:0.75,to:1,持续时间:240})
});
导出函数InlineBox(props:InlineBoxProps){
//…anchorElm、contentElm等的参考资料。
useffect(()=>{
if(等参线和锚定线和内容线){
const-elmStyle=styler(contentElm);
const positionEffect=sticktoceinteraction(主播、内容)
.pipe({x,y}:any)=>({y,x}));
const finalefect=composite({boom:scaleefect,translate:positionefect})
.filter((x)=>!!x.translate)
.pipe((({boom,translate}:any)=>({…boom,…translate}));
const anim=最终效果
.start((v:any)=>elmStyle.set(v));
return()=>anim.stop();
}
},[isOpen,anchorElm,contentElm];
返回(
{anchor(anchorElmRef)}
{isOpen
?{content(contentElmRef)}
:null}
);
}
我研究了其他的实现,如:强> AND< <强> >强>材料< /强>等,但它们似乎没有考虑上述可能性,它们的菜单在设计级别上受到很大限制,具有任何泛型内容。