第一阶段 实现popper能够定位到盒子的上下左右

1 首先获取 当前tooltip元素所在的位置的父级哪个是定位属性 relative/absolute
2 计算出当前tooltip父级定位元素的 getBoundingClientRect 和 refrence的getBoundingClientRect getBoundingClientRect能计算当前盒子的宽高 top是盒子上边缘距离浏览器顶部的距离 bottom是盒子下边缘距离浏览器顶部的距离 左右同理 这里 tooltip父级定位元素的上下左右距离记为tpt tpb tpl tpr 宽高记录为tw th refrence的上下左右距离记为 rt rb rl rr 宽高记为rw rh
注 可见上下左右指的是浏览器的边缘 也就是如果页面上下滚动距离顶部和底部的数据也会实时变化
3 此时可以计算出toolip到refrence的上下左右要走多少距离了

{
top: rt-tpt,
left:rl-tpl,
bottom:rt-tpt+rh,
right:rl-tpl+rw
}

定位再上/下 top-th/bottom 水平居中 left+rw/2-tw/2
定位再左/右 tw-left/right 垂直居中 top+rh/2-th/2

popperOffset:{
top-th/bottom,left+rw/2-tw/2
tw-left/right,top+rh/2-th/2
}
refrenceOffset:{
top: rt-tpt,
left:rl-tpl,
bottom:rt-tpt+rh,
right:rl-tpl+rw
}

以上就是可以准确的定位到要显示tooltip盒子的附近了

第二阶段

接下来的需求是当refrence元素顶到了浏览器的顶部的时候 如果进行一个top位置的定位 tooltip会被溢出 无法看到 解决思路是

计算Boundaries 这个为tooltip的上一个定位属性如果移动浏览器的上下左右边界要平移的距离

tooltip 父级的BoundingClinetRect
tooltipParentPosition=popperParent.getBoundingClientRect()
Boundaries:{
left: 0-tooltipParentPosition.left,
right: document.documentElement.clientWidth-tooltipParentPosition.left,
top:0-tooltipParentPosition.top,
bottom: document.documentElement.clientHeight-tooltipParentPosition.top,
}

源码中preventOverflow方法就是监测tooltip如果移动到refrence上部或者下部的时候是否会被浏览器所覆盖 检测原理就是
1 以向refrence的top移动为例,可以看下第二阶段图片 refrence是顶在浏览器的上边缘 如果tooltip移动到refrence的上面时必然会被覆盖 此时就需要进行检测并重新定位
2 前面已经计算出tooltip到refrence的top要走的距离popper.top 而此时 tooltip父级到浏览器边缘顶部距离是Boundaries.top 因为tooltip开始的时候一直在父级盒子中左上角所以如果移动Boundaries.top这个距离肯定就不会被覆盖 所以可以通过Boundaries.top这个距离和rt-tpt-th这个距离进行检测对比 看是否越界 如果越界的话就要把他重新赋值为Boundaries.top
3 如果检测下部的距离是否越界那么就需要把Boundaries.bottom-th和popper.bottom进行对比

第三阶段
依据第二阶段对比的结果看tooltip是否被翻折下来
1 如果没被翻折下来的话 以tooltip在refrence的上面为例 popper的top就是rt-tpt-th popper的bottom就是rt-tpt-th+th就是rt-tpt 此时可以判断如果没有被翻折下来popper的bottom是和refrence的top是相等的 如果被翻着下来popper的bottom就是tooltip父级的Boundaries的top加上th 此时popper的bottom就会大于refrence的top
2 那么我们就可以依据这个条件来进行判断是否真正的把tooltip变成refrence的bottom


第四阶段
可能有这种情况 就是当refrence高度很高 导致上下都被浏览器覆盖住 那么这时候tooltip无论放在上下都无法被看到 那么此时应该怎么办呢
通过第三阶段我们可以看到当tooltip被覆盖的话会进行翻折 翻折后会进行判断是否进行定位转向 如果这样的话那么就会进入一个死循环 进行无限制的翻折
所以源码的解决方案就是初始化的时候记录原始的定位位置 当递归执行到和原始的定位位置相同的时候就是停止执行 让tooltip fix到浏览器的顶部一直漂浮在那

下面是代码实现

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><style>* {margin: 0;padding: 0;}.box-a {width: 200px;height: 200px;border: 1px solid #ccc;height: 2000px;/* margin-top: 800px; */}.box-b {width: 200px;height: 200px;border: 1px solid blue;}.box {width: 200px;height: 200px;border: 1px solid blue;position: relative;}.box-c {width: 150px;height: 150px;border: 1px solid orange;}.box-d {margin: 100px auto;width: 200px;height: 200px;border: 1px solid red;}.tooltip {padding: 10px 15px;border: 1px solid #ccc;border-radius: 4px;position: absolute;top: 50px;}.box1 {height: 100%;width: 100%;position: absolute;left: 0;top: 10px;}</style><body><div class="box-a"></div><div class="box"><div class="tooltip">tooltip</div></div></body><script>const Default = {placement: "top",//modifiers: ["prevenetOverflow", "flip", "applyStyle"],preventOverflowOrder: ["left", "right", "top", "bottom"],};let boxD = document.querySelector(".box-a");let toolTip = document.querySelector(".tooltip");// function isFixed(element) {//   if (element === document.body) {//     return false;//   }// }// isFixed(boxD.parentNode);class Popper {constructor(refrence, popper, options) {this._refrence = refrence;this._popper = popper;this._options = Object.assign({}, options, Default);this._options.modifiers = this._options.modifiers.map(function (modifier) {if (modifier === "applyStyle") {this._popper.setAttribute("x-placement", this._options.placement);}return this.modifiers[modifier] || modifier;}.bind(this));this._origanlPlacement = this._options.placement;console.log(this._options);setStyle(this._popper, { position: "absolute" });this.update();}update() {let data = { instance: this };data.placement = this._options.placement.split("-")[0];data.offsets = this._getOffset(this._refrence,this._popper,data.placement);data.boundaries = this._getBoundariesRect(data);this.runModifires(data);}_getOffset(refrence, popper, placement) {// 拿到带有position属性的 tooltip的父级的dom节点let refrence_offsets = this._customBoundingRect(refrence,getPoperOffsetParent(popper),placement);let popperRect = getOuterSize(this._popper);let popper_offset = {};if (["left", "right"].indexOf(placement) !== -1) {popper_offset.top =refrence_offsets.top +refrence_offsets.height / 2 -popperRect.height / 2;if (placement === "left") {popper_offset.left = popperRect.width - refrence_offsets.left;} else {popper_offset.left = refrence_offsets.right;}} else {if (["top", "bottom"].indexOf(placement) !== -1) {popper_offset.left =refrence_offsets.left +refrence_offsets.width / 2 -popperRect.width / 2;if (placement === "top") {popper_offset.top = refrence_offsets.top - popperRect.height;} else {popper_offset.top = refrence_offsets.bottom;}}}popper_offset.width = popperRect.width;popper_offset.height = popperRect.height;return {refrence_offsets,popper_offset,};}_customBoundingRect(refrence, parentOffsetDom) {let refrence_rect = refrence.getBoundingClientRect();let parent_rect = parentOffsetDom.getBoundingClientRect();// TODO 暂时不考虑fixed的问题return {width: refrence_rect.width,height: refrence_rect.height,top: refrence_rect.top - parent_rect.top,left: refrence_rect.left - parent_rect.left,bottom: refrence_rect.top - parent_rect.top + refrence_rect.height,right: refrence_rect.left - parent_rect.left + refrence_rect.width,};}runModifires(data) {this._options.modifiers.forEach((fn) => {if (Object.prototype.toString.call(fn) === "[object Function]") {data = fn.call(this, data);}});return data;}_getBoundariesRect(data) {let offsetParent = getPoperOffsetParent(this._popper);let offsetParentRect = offsetParent.getBoundingClientRect();return {top: 0 - offsetParentRect.top + 5,bottom:document.documentElement.clientHeight - offsetParentRect.top - 5,};}}Popper.prototype.modifiers = {};Popper.prototype.modifiers.prevenetOverflow = function (data) {// 判断当前元素到refrence上部的距离和当前元素到页面顶部的距离哪个比较大let popperOffset = getPopperRect(data.offsets.popper_offset);let order = this._options.preventOverflowOrder;let boundaries = data.boundaries;let check = {top: function () {let top = popperOffset.top;if (popperOffset.top < boundaries.top) {top = Math.max(top, boundaries.top);}return {top,};},bottom: function () {let top = popperOffset.top;if (popperOffset.bottom > boundaries.bottom) {console.log(popperOffset.bottom,boundaries.bottom - popperOffset.height * 1);top = Math.min(popperOffset.bottom,boundaries.bottom - popperOffset.height * 1);}return {top,};},};order.forEach((placement) => {Object.assign(data.offsets.popper_offset,check[placement] && check[placement]());});return data;};Popper.prototype.modifiers.flip = function (data) {let refrenceOffset = data.offsets.refrence_offsets;let placement = data.placement;let popperOffset = getPopperRect(data.offsets.popper_offset);let placementOpposite = getOppesitePlacement(placement);let flipOrder = [placement, placementOpposite];if (data.flieped && this._origanlPlacement === data.placement) {console.log("end");return data;}flipOrder.forEach((step, index) => {console.log(placement, step);if (placement !== step || flipOrder.length === index + 1) {return;}placement = data.placement.split("-")[0];placementOpposite = getOppesitePlacement(placement);a = ["right", "bottom"].indexOf(placement) !== -1;console.log(placement,refrenceOffset[placement],popperOffset[placementOpposite]);if ((a &&Math.floor(refrenceOffset[placement]) >Math.floor(popperOffset[placementOpposite])) ||(!a &&Math.floor(refrenceOffset[placement]) <Math.floor(popperOffset[placement]))) {console.log(1, placementOpposite);data.placement = flipOrder[index + 1];data.flieped = true;data.offsets.popper_offset = this._getOffset(this._refrence,this._popper,placementOpposite).popper_offset;data = this.runModifires(data, this._options.modifiers);}});return data;};Popper.prototype.modifiers.applyStyle = function (data) {this._popper.style.top = data.offsets.popper_offset.top + "px";this._popper.style.left = data.offsets.popper_offset.left + "px";return data;};function getOppesitePlacement(placement) {let map = {top: "bottom",bottom: "top",};return map[placement];}function getOuterSize(element) {let _display = element.style.display;let _visible = element.style.visible;element.style.display = "block";element.style.visible = "hidden";let style = window.getComputedStyle(element);let x = parseFloat(style.marginLeft) + parseFloat(style.marginRight);let y = parseFloat(style.marginTop) + parseFloat(style.marginBottom);let result = {width: element.offsetWidth + x,height: element.offsetHeight + y,};element.style.display = _display;element.style.visible = _visible;return result;}function getPoperOffsetParent(element) {var offsetParent = element.offsetParent;return offsetParent === document.body || !offsetParent? document.documentElement: offsetParent;}function addStypeElement(element, key, value) {element.style[key] = value;}function setStyle(element, styles) {let unit = "";function isNum(value) {return value !== "" && isNaN(parseFloat(value)) && isFinite(value);}Object.keys(styles).map((item) => {if (["width", "height", "top", "right", "bottom", "left"].indexOf(item) !== -1 &&isNum(styles[item])) {unit = unit + "px";}element.style[item] = styles[item] + unit;});}function getPopperRect(popperOffset) {popperOffset.bottom = popperOffset.top + popperOffset.height;return popperOffset;}let popper = new Popper(boxD, toolTip);document.onscroll = function () {popper.update();};</script>
</html>

popper.js源码初识研究总结相关推荐

  1. underscore.js源码研究(5)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  2. js define函数_不夸张,这真的是前端圈宝藏书!360前端工程师Vue.js源码解析

    优秀源代码背后的思想是永恒的.普适的. 这些年来,前端行业一直在飞速发展.行业的进步,导致对从业人员的要求不断攀升.放眼未来,虽然仅仅会用某些框架还可以找到工作,但仅仅满足于会用,一定无法走得更远.随 ...

  3. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(github.com/answershuto-)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些产出也会对同样想要学习Vue.j ...

  4. three.js 源码注释(六十一)objects/LOD.js

    商域无疆 (http://blog.csdn.net/omni360/) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:商域无疆 -  本博客专注于 敏捷开发 ...

  5. three.js 源码注释(一)./Three.js

    商域无疆 (http://blog.csdn.net/omni360/) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:商域无疆 -  本博客专注于 敏捷开发 ...

  6. Vue.js 源码目录设计

    Vue.js 源码目录设计 Vue.js 的源码都在 src 目录下,其目录结构如下. src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # ...

  7. 【笔记-vue】《imooc-vue.js高仿饿了么》、《imooc-vue 音乐app》、《imooc-vue.js源码全方位解析》

    20170709 - 20171128:<imooc-vue.js高仿饿了么> 一.第一章 课程简介 1-1课程简介 1.需求分析-脚手架工具-数据mock-架构设计-代码编写-自测-编译 ...

  8. three.js 源码注释(九十五)extras/core/Gyroscope.js

    商域无疆 (http://blog.csdn.net/omni360/) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:商域无疆 -  本博客专注于 敏捷开发 ...

  9. three.js 源码注释(三十一)Scenes/Fog.js

    商域无疆 (http://blog.csdn.net/omni360/) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:商域无疆 -  本博客专注于 敏捷开发 ...

最新文章

  1. Windows PE变形练手1-用PE自己的机器码修改自己的逻辑
  2. 星系炸弹-2015省赛C语言A组第二题
  3. python的二维数组操作
  4. Dlib-人脸识别API说明
  5. 使用 MyEclipse远程调试 Java 应用程序
  6. deepin 惠普打印驱动安装
  7. 105个上榜!2020年工业互联网试点示范项目名单公布
  8. html简历如何转换成pdf,将拉勾的HTML简历转成PDF
  9. Zotero——下载知网PDF文件
  10. 河南联通申请公网ip
  11. 微信小程序开发之——摇一摇Demo
  12. 篮球的各个位置的职能!!!
  13. 阿里品牌数据银行:最全数据银行介绍
  14. 建时间维度表(oracle)
  15. 微信技巧:教你几招导出微信小视频
  16. android对输入手机号码震动,手机卫士输入框抖动和手机震动效果的实现
  17. cura切片软件闪退,添加打印机闪退,导入模型闪退
  18. 语音识别到底是怎么一回事?学习门槛真的那么高么?
  19. 运放构成的电压跟随器
  20. 你真的适合学习JAVA开发吗?

热门文章

  1. 云端计算机可以玩游戏么,云电脑玩游戏效果怎么样?
  2. \\老老实实用百度博客吧,HOHO、
  3. java getproperty_System.getProperty()方法大全
  4. Properties的getProperty
  5. Springboot项目访问路径
  6. 实现手机蓝牙解锁电脑_解答如何实现手机控车 蓝牙无感解锁方案
  7. web进阶任务之备忘录
  8. java毕业设计基于Bootstrap的读书网站设计与实现(附源码、数据库)
  9. 看生产排单软件在冶金项目的应用
  10. Oracle----综合实战:数据库设计分析实例