popup定位引擎popper.js介绍
本⽂译⾃popper.js作者的⼀篇博客
在过去,我为了在web app中更好地定位我的tooltips和popover,我会花⼏个⼩时写同样的⼀段代码,不断进⾏微调。每次我开始⼀个新的项⽬,总会根据不同的环境对定位有不同的需求。这种繁琐直到我⽤emberjs开发⼀个⼤型应⽤时达到极致,这个项⽬中由于⽐较烂的UX设计决定,⼏乎她想在每个元素上都⽀持hover出现⼀个popover!
在这个⼤项⽬中,我们开始使⽤bootstrap3的tooltip,并不断地通过hack代码来实现新的功能。
在这个⼤项⽬中,⼀个重要的需求是:不允许将tooltip元素移动到body元素的直接⼉⼦,因为如果这样的话,我们的代码就会broken.
⼏乎经过⼀年的项⽬开发并且维护我们的定制化实现,我决定使⽤Tetcher,因为他貌似⾮常强⼤和稳定。。不幸的是,玩了⼏个⼩时后,发现他有⼀个重要的缺陷或者说限制:他会⾃动地修改dom节点,将popper移动到body的⼉⼦位置。他会增加⼀些classes并且增加⼀些inline style,⽽这你⼏乎⽆法控制!
于是我⼜到github上花了很多时间去查有没有备选的⽅案,但是我最终⼀⽆所获。难道是没有⼈需要⼀个类似的library?或者还没有⼈能够⽐较好的抽象出来并实现⼀个可以单独发布的lib?
我决定花⼀个周末的事件重写我们⾃⼰项⽬中使⽤的定位engine,这就是popper.js的⼜来。
react tooptip组件在v0.x时代,popper仅仅是⼀个js⽂件,谢了⼏个函数,聚焦在保持lib⼩巧轻量级。我希望popperjs能够有很好的扩展性,所以我决定使⽤middleware system.主要的想法是:计算元素的位置并且允许middleware来根据特定的需求来修改这个位置。
⽐如:⼀旦我们有了popper的position,并给了⼀些边界约束,那么modifier就可以检查popper是否会overflow并且⾃动反转位置以确保popper不会被视窗cut off.
在⼯作了⼀段时间后,我发现我们需要更好的代码结构以便更好地能够维护使⽤他,这就是v1.0版本发布的初衷。
为了管理好代码,我决定切换到es6模块上来。我引⼊了rollup作为code bundler并使⽤babel作为transpiler,我也将⾃动化测试引擎从⼀个⽆头的chrome setup切换到saucelabs cross-browser test suite上来。
在开发过程中,我决定各个功能模块⼀级modifier/middleware都放到他们⾃⼰的⽂件中,这样能在Libray中重⽤。
update流程:
我也重构了整个update流程,也就是每次popper需要更新元素的位置时需要调⽤的代码。
这个update process会在每⼀frame都被调⽤,也就是说⼤概60fps,这样能够保证位置修改的流畅连贯。为了实现这个连贯流畅的⽬标,整个update process的代码必须简练,并且尽可能地避免直接访问dom.下⾯是其⼯作流程
React, Vuejs等第三⽅view library的集成
我必须考虑为了⽀持react或者vuejs需要做些什么,由于这些view library都会直接做dom操作,那么popperjs应该做什么设计的思考
呢?v1.0将所有的dom操作都集中到⼀个modifier中,applyStyle
这将允许使⽤vuejs,react的⽤户简单地disable掉applyStyle并且替换为vuejs,或者react兼容的函数。
Popper.js是如何⼯作的?
popper.js使⽤⼀个reference element(通常是⼀个button或者⼀个link)和⼀个popper element(任何你需要position的元素),popper.js到这两个元素的common offset parent,计算reference element相对于这个parent的位置,然后产⽣出⼀个坐标集⽤于设置popper元素的position.就这么简单。
最困难的地⽅在于必须考虑到⼀些边界条件,⽐如跨浏览器的兼容性,box model的能⼒,必须考虑scrollable element等。简单的⽤法:new Popper(referenceElement, popperElement)
这段代码将会把popperElement放置到referenceElement的下发。⽽且,通过这段代码,你就可以访问所有的内置功能。
1. 如果referenceElement⾮常靠近了viewport的底部,那么popperElement将会被定位放置到referenceElement的上⾯,否则会出现popper 部分不可见的问题;
2. 如果这两个元素位于两个不同的parents,那么popper.js也将会对这种情况考虑周全,也能很好的定位放置popper元素。
3. 它能够有效地处理scrollable elements和页⾯的resize场景.
Popper.js和其他类似的解决⽅案有什么不同呢?
主要的不同点在于该库不需要直接操作dom。这有两个好处:1.他不需要将popper节点移动到另外的context中,⽐如body的⼉⼦,2.这样很
容易将popper.js集成到react,angular,vuejs等view library中。你可以像下⾯的代码⼀样轻松地将dom manipulation delegate给vuejs:
new Popper(referenceElement, popperElement, {
modifiers: {
applyStyle: { enabled: false },
updateReactData: {
order: 900,
fn(data) {
this.setState({ data });
return data;
}
},
},
});
上⾯的代码中,我们关闭了内置的applyStyle modifier,并且定义了我们客制化的modifier,该函数代理获取计算出来的popper坐标⽽将坐标输出到react组件中。
既然你具有了关于popper.js的所有knowledge,那么你就可以对popper element施加任何你想要的样式。
你可能注意到我的定制modifier返回data对象。这个对象是必须的,因为其他的modifier可能会在这个custom modifier之后执⾏,它们也需要使⽤这个data对象。这种链条⽅式的调⽤使得popper.js⾮常易于扩展。你可以注⼊任何定制化的函数在存续modifer之前或者之后运⾏,或者关闭部分modifer,或者修正其他的modifer的⾏为,⽽这只需要通过修改这个data对象数据就可以了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论