第二章:浏览器内核与版本差异——Trident、Gecko、WebKit、Blink的API实现差异

聊到浏览器兼容性,绕不开的就是内核。说白了,内核就是浏览器的“发动机”,不同的发动机决定了你能跑多快、能跑哪些路。

我记得刚入行那会儿,最怕听到“IE6”。那会儿做网站,得先搞定IE6,再去管别的浏览器。后来Chrome出来了,Firefox也争气,再后来Edge也换了芯。这十几年,我真是看着这四个内核“打架”长大的。

内核的前世今生

现在市面上常见的内核,其实就四个:

  • Trident:IE的亲爹,后面Edge继承了一段时间,但后来Edge也“叛变”了。
  • Gecko:Firefox的专属发动机,一直很稳。
  • WebKit:Safari和早期Chrome用的,苹果的看家本领。
  • Blink:Chrome从WebKit分出来的分支,现在大部分国产浏览器也是它的孩子。

为什么会有差异?归根结底是标准实现的时间差和解释的灵活性。

W3C定了个标准,好比定了个菜谱。大厨们(浏览器厂商)看着菜谱做菜,但每个人手劲不一样、火候掌握不一样,做出来的菜味道自然有微妙差别。

浏览器内核技术谱系与API差异 Trident (IE) Gecko (Firefox) WebKit (Safari) Blink (Chrome) 核心API实现差异 CSS特性 -webkit- / -moz- scroll-behavior backdrop-filter Grid布局 CSS变量 (env) DOM API innerText/textContent getComputedStyle document.all attachEvent matches API 事件模型 onclick vs addListener globalEventHandlers 键盘事件keyCode 滚轮事件 拖拽API 适配策略:特性检测 → 前缀处理 → 降级方案 → polyfill

Trident的“遗老”问题

Trident内核,我现在想到它头都疼。为什么?因为它跟标准“离太远了”。

我曾经接手过一个政府项目,老系统必须跑在IE11上。你猜怎么着?一个简单的CSS圆角,在其他浏览器上一行代码搞定,在IE里得写一堆滤镜。

1. CSS滤镜与渐变

IE不支持标准的filter和gradient,它有自己的“亲儿子”:


/* 现代浏览器 */
background: linear-gradient(to bottom, #000, #fff);
/* Trident专属 */
filter: progid:DXImageTransform.Microsoft.Gradient(
    startColorStr='#000000', endColorStr='#ffffff', GradientType=0
);

我建议,除非业务强求,否则果断放弃IE10以下。你想想看,为了0.01%的用户,维护两套代码,不划算。

2. 事件模型的“特立独行”

Trident的attachEvent vs 标准的addEventListener。这个坑我踩过太多次了。


// 教科书式的兼容写法
function addEvent(el, event, handler) {
    if (el.addEventListener) {
        el.addEventListener(event, handler, false);
    } else if (el.attachEvent) {
        el.attachEvent('on' + event, handler);
    } else {
        el['on' + event] = handler;
    }
}

注意,attachEvent里的this指向window,不是触发元素。嗯,这里要注意,一不小心就翻车。

Gecko的“固执己见”

Gecko,Firefox的御用内核。它很守规矩,但有时候规矩得让人抓狂。

1. innerText vs textContent

这个坑很有意思。Gecko只认textContent,而innerText是IE发明的,后来也被其他浏览器接受了。但Gecko一直“傲娇”到很晚才支持。


// 兼容写法
const text = el.textContent || el.innerText || '';

2. CSS前缀的“拖沓”

我记得Chrome和Safari早早地用上了-webkit-前缀,而Gecko用-moz-。最头疼的是,一些实验性API,Gecko的实现跟标准文档总有偏差。

比如,scroll-behavior: smooth;,Gecko在v36才支持,而Chrome v61才开始支持。中间那段日子,用户只能“硬跳”。

个人经验:在团队里,我总让新人先写标准属性,再写兼容前缀。别问为什么,问就是标准才是未来。

WebKit的“封闭花园”

WebKit,Safari的专属内核。它有个特点:跟标准接轨,但总喜欢加点“私货”。

1. -webkit-前缀的“泛滥”

早期的Chrome和Safari共用WebKit,但Chrome后来分家成了Blink。

WebKit的私有前缀不仅多,而且有的属性后来成了标准。比如-webkit-sticky,现在已经是标准的position: sticky了。


div {
    position: -webkit-sticky; /* 老Safari */ 
    position: sticky; /* 标准 */
    top: 0;
}

但要注意,-webkit-sticky在旧版Safari上不支持overflow为hidden的父元素。这个坑,我在做H5活动页的时候栽过。

2. 性能差异:requestAnimationFrame

WebKit对这个API的实现有个特点:页面不可见时,它不调用了。

这其实很合理,省电嘛。但如果你用它做定时器,严格依赖帧率,就得小心。我建议用setTimeout兜底,或者检测document.hidden状态。


function animLoop(callback) {
    let timer;
    function loop() {
        callback();
        timer = requestAnimationFrame(loop);
    }
    timer = requestAnimationFrame(loop);
}

Blink的“激进与融合”

Blink从WebKit分出来之后,直接脱离了苹果的“管制”。它更激进,也更贴近标准。

1. 剪贴板API

Blink率先支持了navigator.clipboard,而其他内核还在用document.execCommand。


async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
    } catch {
        // 回退方案
        const textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
    }
}

你猜怎么着?这个API在HTTPS环境下才能用。本地开发用localhost没问题,上生产必须配SSL。

2. 容器查询的支持

Blink在v105率先支持了@container,而Safari和Firefox到现在(2024年)才陆续跟上。

如果你的项目主要面向桌面Chrome用户,大胆用。但如果是移动端,我建议再等等,毕竟Safari在iOS上的话语权很重。

避坑指南: 我曾经在生产环境直接用了@container,结果iOS用户一打开,布局全乱了。从那以后,我学会了先查caniuse,再落代码。

差异溯源:表格说话

我整理了一份常见API差异的对照表,你们可以直接拿去当开发参考:

API / 特性 Trident (IE11) Gecko (Firefox 120+) WebKit (Safari 17+) Blink (Chrome 120+)
CSS padding和margin百分比基准 参照父容器宽度 参照父容器宽度 参照父容器宽度 参照父容器宽度
clearfix方案 依赖zoom:1触发hasLayout 使用::after伪元素 使用::after伪元素 使用::after伪元素
clipboard API 仅execCommand 支持Async Clipboard 支持Async Clipboard 优先支持Async Clipboard
CSS Grid布局 不支持 支持(v52+) 支持(v10.1+) 支持(v57+)
position: sticky 不支持 支持(v32+) 支持(v6.1+) 支持(v23+)
ResizeObserver 不支持 支持(v69+) 支持(v13.1+) 支持(v64+)
SVG favicon 不支持 支持 支持 支持
CSS env() 安全区域 不支持 支持(v101+) 支持(v11+) 支持(v69+)

你看,光一个position: sticky,Trident就彻底扑街了。所以,如果你的用户里还有IE11,这个属性你就得降级成position: relative加JS监听滚动。

适配策略:我的三件套

面对这些差异,我一般用三招:

  1. 特性检测(Feature Detection):用if (typeof xxx !== 'undefined')或者@supports规则。
  2. 前缀处理:借助Autoprefixer这个工具自动生成,别手写-webkit-,你会疯的。
  3. Polyfill:对缺失的API(比如IntersectionObserver),用第三方库给它补上。

我个人习惯:能检测就检测,不能检测就降级,降级不了就报错。别在IE上浪费太多工时,你的项目又不是给博物馆做的。

对了,还有一点:移动端优先考虑WebKit/Blink。因为iOS上只能用Safari(WebKit),Android上大部分是Chrome(Blink)。Gecko在移动端的份额,基本可以忽略。