position:fixed定位问题的解决

本篇文章介绍了一个在开发中遇到的诡异的问题,排查问题过程颇为艰辛,不过最终结果还是值得的,因为巩固了一些基础知识和好的调试方法,它们是:

  • fixed定位的特点
  • 样式优先级的判定
  • 如何使用浏览器的DOM还原一闪即逝的错误效果

1. 问题说明

在猎萝卜-猎头端,测试提出这样一个bug(禅道编号12828):

在猎萝卜的 订单 - 我的职位 - 已接单 入口,点击右侧职位列表的任意职位,在弹出职位预览之前(图2),会先看到职位详情的底部串到左下角位置(图1)

图1 异常状态
图2 回到正常的状态

2. 调用过程

线框说明图1
线框说明图1

首先介绍下触发整个动作的逻辑,为了方便理解,借用上面的线框图来说明:

  • projectList.vue是职位列表,其中每个职位对应的条目封装成了order-project-item.vue,点击order-project-item.vue后,会触发事件全局的expandOrderPreview
  • order-project-preview.vue作为一个常驻在界面右侧的组件,默认是收起在屏幕右侧的,在侦听到了expandOrderPreview事件以后,会从右侧弹出
  • next-bar-affix.vue作为order-project-preview.vue底部的子组件,会一起出现和消失

3. 具体的代码逻辑

在下图中的逻辑代码,整理成如下描述:

order-project-item.vue触发点击事件expandOrderPreview,被order-project-preview.vue侦听到,然后进行如下处理:

  • 第一步 emit showOrderPreview事件

    • 父级app-frame.vue收到showOrderPreview事件,给引用的order-project-preview.vue添加一个.previewing的样式
    • .previewing会使order-project-preview.vue的宽度从0变到特定宽度从而在右侧展示
  • 第二步 调用refresh方法

    • loading设置为2,order-project-preview.vue中详情面板受到loading==0v-if控制,因此会隐藏;loading组件发现loading>0因此会有加载效果
    • 异步调用getProjectDetailgetOrders 这两个方法,直到结果返回,loading值会递减至0,详情才开始正常展示,加载效果才会去除
order-project-preview.vue侦听到弹出事件
app-frame.vue被order-project-preview.vue的emit触发进行previewing样式添加
order-project-preview.vue的refresh方法的逻辑
order-project-preview.vue的模板

4. 一些基础知识

解决问题之前,通过查看样式代码,我们巩固了一些基础知识。

如下是抽取出来的DOM结构和样式设置:

<div class="app-frame">
  <div class="order-project-preview">
    <div class="next-bar-affix">
      <div class="h-affix" style="bottom:0"></div>
    </div>
  </div>
</div>
.order-project-preview {
  position: fixed;
  top: 0;
  bottom:0;
  right: 0;
  width: 300px;
}
.next-bar-affix {
  position: fixed;
  bottom: 0;
}
.next-bar-affix .h-affix {
  position: fixed;
  height: 60px;  
}

  • .order-project-preview进行position: fixed设置后,DOM的位置会相对视口固定,通过left, right, top, bottom来设置定位
  • 在上述示例代码中,.order-project-preview宽度300px,高度撑满屏幕,底部栏 .next-bar-affix底部内容区的高度为30px
  • 其中.next-bar-affix部分的代码是由heyuiaffix组件生成的,一个有意思的启发是,在.h-affix中也添加了一个bottom:0的属性。可以尝试将该属性去除,会发现底部的内容区域会跑到最底部之下,变得不可见。
  • 综上,可以从上述效果图推测的一个结论是:

如果一个子容器设置了fixed定位,它的父容器也是fixed,那么在不指定子容器的left, right, top, bottom时,子容器的边界会以父容的top left作为边界。进一步可以推断,在我们的问题中,必然是有什么因素使得作为父容器的.order-project-preview失去了作为子容器定位基准的条件**,从而跑到了视口的左下角。

5. 具体推测

做过的一些不靠谱的推测:一个DOM样式的变化 A->A+B 实际上是 A -> 无 -> A+B,即order-project-preview.vue增加 .previewing的过程中,容器样式到的中间过程暂时使得next-bar-affix.vue丢失了left的基准,从而导致在中过程中串到了视口左下角。

this.$emit('showOrderPreview');
this.refresh();

基于上述推测,为了解决这个问题,考虑将上述两行代码对调:

  • 先让refresh的逻辑去触发order-project-preview.vue的隐藏,自然也就隐藏了next-bar-affix.vue
  • 再触发showOrderPreview事件来order-project-preview.vue的展开
  • refresh触发的异步调用结束以后,order-project-preview.vue中的具体内容才连同next-bar-affix.vue被展示出来

但结果不如预期的那样,猜想也许是refresh 成功隐藏next-bar-affix.vue之前,this.$emit('showOrderPreview');的逻辑引起的弹出生效了,于是添加了如下代码(事实上纯属没有依据的HardCode):

this.$emit('showOrderPreview');
this.$nextTick(() => {
  this.refresh();
})

运行,没在出现如上所示的问题,似乎是解决了。

6. 问题又重现了

然后,提交代码后,测试同学告知,该问题又重现了,重现步骤姑且略过,这时候联想到之前排错的过程中,就猜想可能跟浏览器的渲染顺序有关系,然后把浏览器的工作原理的基础介绍翻阅了一遍,但并没有解答这个问题。

又想起团队的caoq同学分享过如何监听DOM上的属性变化。问题难点无非在于无法让浏览器在某个异常的时刻停下来,如果能停下来观测DOM的结构和样式,差不多就可以排查到问题根源。于是给order-project-preview.vue的根节点设置了attribute modification的断点(设置方法如下图所示):

image.png

这时候意外地发现在异常发生时,主界面order-proejct-preview.vue的内容已经加载出来了(但却没显示),即不存在next-bar-affix.vue 丢失默认的left基准的问题。

倒是发现了该容器上添加了一个 .h-loading-parent的样式,这个样式定位为 relative 。由此基本能确定了在heyuiloading组件的渲染中,会修改所在父容器order-project-preview.vue的样式,给它添加上.h-loading-parent

因为 order-project-preview.vue 最外围默认的样式是 fixed, 被.h-loading-parent覆盖之后变成了 relative(它们具有相同的优先级,后出现的relative覆盖前出现的fixed),从而导致了next-bar-affix.vueleft基准丢失。我们只要把给弹出的面板order-project-preview.vue加上更高优先级的fixed定位即可。

//loaing组件提供 样式
.h-loading-parent {
  position: relative;
}

// 自书写的样式
.order-project-preview-page {
  // 两个class累积的优先级为2,大于.h-loading-parent的1:
  &.previewing {
    position: fixed;
  }
}

7. 再深入一些

问题:断点过程已经渲染好的面板却看不到内容,只看到一个错位的底部栏

通过查看heyui<loading>组件源码的样式可以发现:

  • heyui组件在loading完毕后,会设置500mstimeout来清除.h-loading-parent的样式(参加下图1),即内容渲染出来了,但是样式500ms内还没去除,也就导致了relative引起的错位
  • relative的属性同样影响到了 .order-project-preview-page.previewing 的高度,因为它的各个子元素无一例外都absolute的,因此这样的文档流无高度的,而被覆盖的fixed,通过了 top',bottom`赋予了它和视口相同的高度。
Loading的渲染代码
问题2 我们在本地代码上无法重现该问题

在本地代码运行在浏览器的样式发现:.order-project-preview-page上设置的fixed的优先级高于 h-loading-parentrelative

因此可以做一个猜测

  • 在开发环境中库的样式被先加载,自己组件内地样式后加载,如果计算的优先级的值一样的话,自己组件的样式自然就失效了。
  • 在线上环境,样式的代码经过压缩以后,并不能保留开发环境中的出现顺序,因此需要人为的设置来保证所期待的优先级。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容