几乎失传的传统技能分享 - 兼容 IE11

319 阅读7分钟

可以不看的前言

周五晚上加班加到将近 10 点,和同行开玩笑说我恨 IE 11,他说居然还要兼容 IE 11,这种传统技能几乎已经失传。微软在去年 9 月份已经发布声明说自 2022 年 6 月 15 日 起,不再维护 IE 11 了。但是我们的业务方不放弃 IE 11,客户也不一定放弃 IE 11,所以我们也不能放弃。

本文旨在分享我在兼容 IE 11 过程中踩过的一些坑以及我选择的解决方式,并不是最佳实践。在一定程度上只做到了知其然,没能做到知其所以然。极客时间的一门课程《浏览器工作原理与实践》的主讲人李兵说,浏览器是基于一套套标准来实现的,比如页面标准(W3C)、语言标准(ECMAScript)、网络标准。但是在浏览器的发展中,一度制定和实现了很多标准,但其中一部分标准并没有得到广泛的应用。不同的浏览器厂商在开发浏览器时选择的标准可能会不一样。这就是我理解的不同浏览器之间存在兼容性问题的原因。要搞清楚为什么 IE 11 下 会有这么多"坑",可能要去了解当时是基于什么样的标准来实现这些功能的,这又是一个很庞大的话题了。


交代完废话,下面我会从 HTML、CSS、JavaScript 这 3 个方面展开来讲,其中涉及的兼容性问题除了 IE 11 ,还有Safari、iOS 和 Android。

HTML

1.<input>标签 readonly 属性不生效。

我们需要做一个类似 ElementUI 下拉选择框的组件:

image.png

因为 reset HTML 原生<select>标签的样式太麻烦,并且原生事件没能满足业务场景,我参考了 ElementUI 输入框用 <input>,下拉选择部分用<div>的写法。<input>自带 placeholder,下拉选择框中的提示不用额外定义。我们的下拉选择框不需要输入功能,设置 readonly 属性即可 👇

<input type='text' placeholder="请选择" readonly />

这种写法在 IE 11下会有一个问题,即使设置了 readonly,光标还是可以移到输入框,光标 focus 到输入框会导致 placeholder 消失。虽然 Can I Use中提到 IE 11 支持 readonly,但是我用了并不好使。

<input>设置 disabled 属性,能达到在光标不出现在输入框的目的,但是会导致 <input> 的颜色“变灰”,也就是视觉上有禁用的效果。这么一来,又多了重写 CSS 样式的工作。

于是我想一个很鸡贼的方法,在 <input> 上面放置一个和<input>一样大小的透明<div>,这样无论在任何浏览器中都不会有兼容性问题,也不需要考虑重写样式了。

另外有人提到可以给<input>设置 unselectable=on,不确定是不是打开方式有问题,我试了行不通。

CSS

1. 以 <img> 标签的方式展示 svg 图片,设置 width 不生效。

<img src="./assets/images/example.svg" class="example" /> 如果直接给 .example 设置宽度,在 IE11 下图片依然会展示为它的原始 size。参考 Fix SVG in <img> tags not scaling in IE9, IE10, IE11 的 做法,我的解决方式是给 <img> 增加一个父元素, 再给父元素设置宽度,<img>宽度设为 100% 。

<div class="example-wrapper">
  <img src="./assets/images/example.svg" class="example" />
</div>
.example-wrapper {
  width: 50px;
}
.example {
  width: 100%;
}

2.父元素 just-content: center,IE 11 下不居中。

现在手头上没有 IE 11 运行效果图,画两个示意图给大家感受下区别:

image.png

image.png 不确定是否是 IE 11 在 flex 属性的容器中,只居中排列了有显式设置宽度的子元素。后来直接给 right box 显式设置了一个固定宽度,IE 11 下的页面就能居中展示了。

JavaScript

1. new Date 将字符串转换成日期对象返回 Invalid Date

2021-12-03 23:59:59 这种格式 在 IE 11 和 Safari 下 用 new Date 转换成日期对象会返回 Invalid Date。像这样👇

image.png

把“空格”替换成“T”能返回正常转换成日期对象,但是依然有个问题,同样是 GMT 时间,不同浏览器算出的时间是不一样的👇

image.png

image.png 可以看到,在 iOS (目前发现是部分 iOS)下,算出的时间比 Chrome 快了 8 小时。后面跟后端同事商量过,直接改成在接口返回时间戳(伸手党),向浏览器低头。

2. IE 11 和 部分 Safari 缓存 get 请求的内容

我们有个场景是每隔 3 s 调一次后端的订单状态查询接口,request Method = get,URL 后面不带任何参数,也就是每一次查询的 URL 都是一样的。在测试过程中发现,用 POSTMAN 调接口,状态已经变更了。但是在IE 11 的控制台看到,浏览器有每隔 3 s 发出一次请求,但是每一次轮询的接口获取到的订单状态依然是未变更的。后来请后端查日志发现只收到一次请求,加上 POSTMAN 调的那次一共只有两次。后来通过查阅资料发现,不单单是 IE 11,Safari 也可能会缓存 get 请求的 response。

在请求 URL 后面拼一个无任何意义的时间戳,确保每一次发起的 get 请求都是不一样的,可以解决问题。

const url = `/api/order/status?timeStamp=${new Date().getTime()}`
// 或者
const url = `/api/order/status?_=${new Date().getTime()}`

3. IE 11 移除 DOM 节点

移除 DOM 节点有一个非常省心的 API:element.remove()。但是可爱的 IE 11 并不支持这个API,不过我们有替代的方案,幸好 IE 11 是支持 element.removeChild ()的。所以这个方案应该在大部分浏览器都不会报错:targetElement.parentNode.removeChild(targetElement)。翻译一下这段代码:先把目标 DOM 节点的父节点找到,再让父节点把它 remove 掉。

说句题外话,我在写这段代码的时候觉得有点搞笑,这个场景很像遇到一个熊孩子,先把他爸拎出来,让他爸把他给解决了。

  1. IE 11 下 NodeList 使用 forEach 方法 NodeList 是一个类数组对象。遍历 NodeList 是一个非常常见的场景。MDN 很贴心地给出了将类数组对象转换成数组的方式:Array.prototype.slice.call(arguments),也可以简写成 [].slice.call(arguments)。有兴趣的同学可以移步到 MDN 查阅详细的文档。

回到我们的问题,在 IE 11 下可以这样用 forEach 遍历 NodeList👇

[].slice.call(NodeList).forEach((node) => {
    // do something
})

写在最后

上面提到的这几个兼容性问题不过是九牛一毛,如果在日常开发中需要兼容到比较多版本的浏览器,随时打开 Can I use和 MDN 拉到页面底部查看浏览器支持情况是个不错的习惯。假设遇到了兼容性问题也不用慌。在确保自己已经认真思考过、努力找过方法但是还是没能解决时,并且在保证不影响同事的工作的前提下,可以和经验丰富的同事聊聊自己的困惑,说不定同事可以提供新的思路帮助解决问题。白嫖怪(我🤡)基本上都是在同事的帮助下解决上面提到的几乎所有问题的。感激我的同事,让我欠下了 5 顿饭的巨债。

在解决兼容性问题的过程中,调试技巧也是得力助手,可以了解下 Mac 如何调试 iPhone 的 web page 。习惯用 Macbook 开发的同学,也可以用一些应用程序通过远程链接 windows 的方式,在自己的电脑一边开发一边调试。