我的2018前端踩坑记 | 掘金年度征文

681 阅读10分钟

某著名小白说过 :世上本来到处都是坑,只要走的人多了,也就把坑都给埋了。该小白还说过:坑本身并不可怕,可怕的是踩了一次之后,还第二、第三次踩到了相同的坑。

所谓"坑",主要是由于我们对某些知识点理解不够透彻,导致在应用的时出现了一些奇怪的问题。因为我们每个人,对于某个知识点的理解程度不一样,所以,有些坑我觉得真的很坑,但是你可能觉得一点都不坑,因为你早就对它了如指掌了。

这里列举的一些坑,都是我过去一年在项目中所遇到过的,并当时在笔记中记录下来的,现在稍加整理就形成了这篇博客,以供日后查阅。

不知不觉,开头又bb了这么多,还是赶紧进入正题哈。

1. 设置透明度(opacity)引起的惨案

之前做炉石盒子的天梯环境页面,地址是 炉石天梯环境 ,就在项目做得差不多的时候,准备上线了, QA 突然发现了如下的一个 bug:

有一个选择排序方式的下拉菜单,它的定位是 position: absolute,正常来说,它应该会覆盖在其他元素之上的,可是为什么 0.14% 反而会覆盖在它上面呢? 在代码中找了好久,那个 0.14% 并没有设置 z-index属性,也没有 position: absolute 这样的东西,真是好郁闷哦。后来到 mdn 查文档才发现,原来是 opacity属性引起 的: opacity 属性值小于 1 的元素会创建新的层叠上下文 。因为当时我有个偷懒的做法,字体继承的颜色是 #666, 我想让 0.14% (天梯比率)颜色变浅一些,直接加了个 opacity: 0.6 ,导致了创建了新的层叠上下文,层级比下拉菜单高了,所以就覆盖在了上面。具体什么是层叠上下文,以及哪些属性会创建新的层叠上下文,这里也不介绍了有需要的可以参考一下 层叠上下文

虽然上面描述得已经很详细了,但是可能由于我的表达能力不太好,有些朋友还不是很明白我的意思,可以看一下这里的 demo 代码:

<div class="menu">
  <div class="title">下拉菜单</div>
  <div class="menu-list">
    <div class="item">菜单1</div>
    <div class="item">菜单2</div>
    <div class="item">菜单3</div>
  </div>
</div>
<div class="content">
  我是半透明的文字,可以覆盖在下拉菜单之上哦~
</div>
<style>
  .menu {
    position: relative;
  }
  .menu-list {
    display: none;
    position: absolute;
    background: #ccc;
  }
  .menu:hover .menu-list {
    display: block;
  }
  .content {
    opacity: 0.6;
  }
</style>

将鼠标移动到下拉菜单上,就会发现文字会发生重叠了:

那这个坑有什么解决办法呢?最简单的就是下拉菜单添加个属性 z-index: 1 。另外,这里再啰嗦一下,就是z-index的值不要乱设置。以前刚刚接触前端时,会经常看一些视频教程,看到里面讲师动不动就设置个 z-index: 999 之类的特别大的数值。这是一个不好的习惯。张鑫旭老师在《CSS世界》一书中,提到了 不三原则,就是说一般情况下,z-index的值不要超过3,基本能满足大多数的需求了。

2. flex布局:子项溢出后无法查看全部内容

之前做漫画阅读器,因为漫画可能有长图片,也可能有短图片。长图片可以滚动查看,短图片就居中显示。所以,很自然会想到用 flex 布局来实现。简单的代码如下:

<div class="app">
  <img src="https://m.tuniucdn.com/fb2/t1/G1/M00/F1/51/Cii9EFkAaZ-IRgGNAATB18ldk0UAAJzuQN-p1cABMHv15.jpeg" alt="">
</div>
<style>

  html,
  body {
    height: 100%;
  }

  .app {
    display: flex;
    height: 100%;
    justify-content: center;
    align-items: center;
  }

  img {
    width: 100%;
  }
</style>

这里,我们的 .app 容器里面这里有一张很长的图片。当我们运行上面的代码,如果你仔细观察原图和页面显示的图片,就会发现图片的顶部和底部的一些内容看不到了,滚动条到了一定位置就无法滚动了。正常来说,我们应该可以通过滚动条的上下滑动看到图片的全部内容才对的。当时我想了很久也没有想出来原因,最后到 stackoverflow 找到了答案 Can't scroll to top of flex item that is overflowing container

答案中有提到,可以设置子项的 margin: auto 来实现内容溢出时也自动居中(包括水平和垂直的):

修改后的 CSS 代码如下:

  .app {
    display: flex;
    height: 100%;
  }

  img {
    width: 100%;
    margin: auto;
  }

所以,以后如果在使用 flex 布局实现居中,如果子项的内容会溢出 flex 容器 ,可以将子项设置为 margin: auto 试试。

3. transfrom 和 fixed 不能在一起!

CSS3 的 transform 属性也算是比较常用的,特别是做一些动画效果的时候,用它来移动元素的位置,性能会比设置 top 或 left 要高一些。但是,如果一个元素设置了 transform 属性,而它的子元素又设置 fixed 定位,那么这个 fixed 定位的子元素表现会有些奇怪,如下代码:

<div class="app">
  <button onclick="layer.style.display='block'">弹出蒙层</button>
  <div class="layer" id="layer"></div>
</div>

<style>
  .app {
    position: relative;
    width: 100px;
    height: 100px;
    background: #ccc;
    /* 使用transform让元素向下偏移20px */
    transform: translate(0, 20px);
  }

  .layer {
    display: none;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: rgba(0, 0, 0, 0.7);
  }
</style>

打开页面,效果是这样的:

我们希望点击“弹出蒙层”按钮后,就显示个覆盖整个窗口的蒙层。但是结果却出乎意料。如下:

蒙层只是遮住了小正方形,这不是我们想要的结果,因为我们知道,fixed 定位是相对于屏幕视口(viewport)定位的, 但是,本例却相对于它的父亲元素。这是为什么呢?如果认真查阅 mdn文档 ,就会找到答案:

可见,问题的原因在于,我们只是记住了fixed是相对于 viewport 定位,但是也有特殊情况: 当元素祖先的 transform 属性非 none 时,容器由视口改为该祖先 。所以,这并不是bug,是因为我基础不扎实导致的。事实上, 除了 transform 会改变 fixed 的定位元素之外,还有其他属性也会改变,ChokCoco大佬有一篇文章做了详细的讲解,想要了解更多的请点击 不受控制的 position:fixed

那遇到这种情况怎么办呢?比较好的办法就是把 fixed 元素移到外面去,不要放到有 transform 属性的元素里面。但是,有时候我们没有办法移到外面怎么办呢?比如,这是它是一个子组件,它的某个父亲组件就是用了 transform,那怎么办呢?我也不知道怎么办,欢迎大家探讨一下哈哈哈~

fixed 定位还具有其他的坑,这里也不展开了,有兴趣的可看 看github 上有大佬整理好的这篇文章 移动端web页面使用position:fixed问题总结

4. 安卓微信视频播放器的层级问题

我们都知道, video 标签设置了 playsinline 就可以内联播放视频,而不是全屏播放。(注意:前提是客户端的 Webview 配置了允许内联播放,所以有时候虽然设置了 playsinline,但在某些 app 里面打开依然是全屏播放,这不是前端的锅哦)。最近有一个需求,类似下面这样的:

页面上有一个视频,视频播下面有一个按钮,点击按钮就弹出一个图片,该图片要覆盖整个屏幕,比如是这样的:

示例代码如下:

<video id="video" controls="" playsinline="" src="https://vod.cc.163.com/file/5bcbe1ae9efdc0608bb6d06b.mp4"></video>
<img id="image" src="https://ds.163.com/2018/mrzh/appointment/static/img/bg-body.cdef1ec.jpg" alt="">
<button id="button">弹出图片</button>
<style>
  video,
  img {
    width: 100%;
  }

  img {
    display: none;
    position: absolute;
    top: 0;
  }
</style>

<script>
  var video = document.getElementById('video');
  var image = document.getElementById('image');
  var button = document.getElementById('button');
  button.onclick = function() {
    image.style.display='block';
  }
  image.onclick = function() {
    this.style.display='none';
  }
</script>

但是,在安卓上却发现一个问题,开始播放视频后(注意,只有播放视频后才可以复现),点击“弹出图片”按钮,显示如下所示:

图片无法覆盖在视频播放器上面。然后,我设置了 z-index 或者 transform ,都没有任何效果。最后, 剩下的可能原因就是: 安卓微信视频播放器实际上用的是原生组件。为了验证这一猜想,我们可以启用开发者选项的绘图模式 (开发者选项 --> 绘图 --> 显示布局边界,不同机型不一样,找不到的请百度找一下哈),结果如下:

看到没有,视频是一个完整的有边框的东西,证明他是一个独立于 Webview 的原生组件。

那怎么办呢?只能上网找答案呀!我们都知道,微信 webview 使用的是 X5 内核,所以我也希望能从它的开发者文档上找到一些有用的信息,好不容易找到了一篇叫做 H5同层播放器接入规范 。它说可以可以在 video 标签添加一个属性 x5-video-player-type ,并且给出的例子是这样的:

<video src="http://xxx.mp4" x5-video-player-type="h5"/>

我当时很高兴,以为问题就这样解决了,然并卵,添加了 x5-video-layer-type 属性之后,playsinlie 属性就失效了,无法内联播放了,只能全屏播放,所以,不能添加这个属性。

然后我就想,既然无法覆盖这个视频,那在弹出图片的时候能不能把视频给隐藏掉?然后关闭图片的时候再把视频显示回来呢?于是就把 JS 代码改成下面这样:

  button.onclick = function() {
    image.style.display='block';
    video.style.display = 'none';
  }
  image.onclick = function() {
    this.style.display='none';
    video.style.display = 'block';
  }

然后,这样就可以了。因为我在网上找不到更好的办法,如果大家遇到这个问题可以参考这种做法。当然,如果你们找到了有更好的办法,欢迎评论分享出来哈~

关于安卓微信视频播放器的坑就先讲到这里啦。

等等,一讲到原生组件,这里还得再补充一下微信小程序相关的东西,当然,我自己还没有过小程序的开发的经验, 这是之前的一次内部交流会,一位同事的分享:小程序在渲染的时候,大多数组件都是渲染成 HTML 组件,但是有少部分比如 canvas、 video、 input、 map 等会渲染成原生组件的。所以,如果你在写小程序时,想用一段文字覆盖在一个 canvas 上,发现怎么设置都无法实现,那是因为 canvas 渲染后是原生组件,而文字是 html 组件,所以无法覆盖上去的 。那有什么办法呢?可以考虑把文字放到 cover-view 上, 它也是一种原生组件,可以覆盖在 canvas 上的。具体的可以参考小程序官方文档 原生组件说明

关于2018踩的坑就写到这里了,当然还有一些其他的,暂时没有时间整理,下次如果整理后,再写一篇补充一下。

如果大家有什么问题,或者过去踩到过了哪些坑,欢迎在评论区讨论哈。

掘金年度征文 | 2018 与我的技术之路 征文活动正在进行中......