
接手一个老系统改造需求单上只有一句话页面加全屏水印防止内部资料被拍照外传。看着像个五分钟的活儿真正卡住的是后面那句附加条件——必须兼容 IE7且水印不能盖住页面上的按钮、输入框和下拉框。水印兼容 IE7 这件事难的从来不是画出来而是画上去之后别碍事。现代浏览器一行pointer-events: none就解决的事到了 IE7 上完全失效因为 IE10 及以下根本不认这个属性。于是水印层要么被降级成一个吃事件的透明盖子要么就被迫换一种存在形式。这篇内容面向两类人一类是还在维护内网老系统、被指定兼容低版本 IE 的前端另一类是虽然不写 IE但想搞清楚水印层为什么不挡交互这个底层逻辑的人。我会把三条可行路线、每一处的参数选择、实际踩到的坑和排查手法完整摊开代码可以直接拿去改。1. 先把需求拆开看三个问题其实是一件事水印覆盖控件这个描述在实际排查里往往对应两种完全不同的故障混在一起谈就容易治错病。一种是视觉上的覆盖——半透明水印压在按钮上按钮整体发灰、文字对比度下降用户看着像禁用状态但点下去其实是有反应的另一种是交互上的拦截——水印层作为一个独立 DOM 元素横在最上层鼠标事件被它接走底下的输入框聚焦不了、链接点不动、下拉框展不开。前者靠调透明度、调层级、调水印密度就能缓解后者必须动结构或动事件模型。我在需求评审时专门把这两条写在白板上分开讨论因为它们的解法几乎没有交集。1.1 水印为什么必须能看不能摸水印的产品定位很明确它是一层视觉留痕不是一层操作界面。这意味着它在文档流里的角色应该无限接近于背景——用户能看见但所有鼠标和键盘行为都应该穿透过去落到真正的内容上。理解这一点后面所有的方案取舍就都有了判断标准任何让水印层参与事件命中的实现本质上都是错的方向只是有时候不得不用补丁去修正它。我见过不少项目把水印做成一个divposition: absolute、z-index: 9999、background: url(wm.png) repeat然后祈祷用户不会点到被盖住的区域。这种写法在展示型页面上勉强能用一旦页面上有表单、有操作按钮问题立刻暴露。更麻烦的是它属于偶发型故障——用户点十次有一次没反应反馈上来就是系统卡排查成本极高。判断一个水印实现是否合格我一般看四个指标水印是否随内容滚动、是否遮挡文字可读性、是否影响任何控件的鼠标行为、以及在页面 DOM 结构变化比如弹窗后是否还能正常显示。这四条里第二条和第三条经常互相冲突这也是为什么需要专门设计而不是随手加个背景图。1.2 IE7 的能力清单决定了方案上限动手之前必须把目标浏览器的能力边界摸清楚否则设计方案就是空中楼阁。IE7 的很多限制在今天看来匪夷所思但在内网终端、老版本办公环境里它就是硬约束。我把和水印相关的关键能力整理成下面这张表后面所有方案都是围绕它推导出来的。特性IE7 支持情况对水印方案的影响pointer-events: none不支持IE11 才支持 HTML 元素无法用一行 CSS 让水印层不接收事件opacity不支持只能用filter: alpha(opacityxx)做半透明rgba()不支持不能用 CSS 颜色做半透明背景PNG 24 位透明通道支持水印素材可以直接用带 alpha 的 PNGposition: fixed标准模式下支持但与滤镜配合会抖动固定水印仍建议用 JS 模拟滚动定位document.elementFromPoint支持可以做精确的事件命中转发addEventListener不支持只能用attachEvent(onxxx, fn)CSSbehaviorHTC支持可作为事件穿透的实现手段但部署成本高VML支持可作为平铺水印的兜底渲染方案select元素层级窗口级控件永远置顶任何 div 都盖不住它反过来它也不会被水印遮住filter与层叠上下文加了 filter 的元素会独立成层z-index行为会变得不可预测这张表里最容易被忽略的是最后两条。select的置顶特性意味着如果你的水印层盖在了一个下拉框上视觉上你会看到一个破洞——下拉框穿透了水印显示在最上面水印在它周围环绕看起来像是渲染错位。这个现象经常被误判成样式 bug实际上是 IE 的渲染机制决定的改样式改不出来只能改结构。1.3 三条路线先摆出来对比明确了约束方案空间其实就三条把水印变成背景、把水印层降到底部、让水印层主动让路。三条路各有适用场景我先把对比摊开后面的章节再逐个深入。方案核心思路优点局限背景图平铺水印作为容器的background-image天然不参与事件命中无需任何兼容处理会被有背景色的子容器遮挡需要逐层补层级下沉水印层z-index: -1内容容器在上层结构简单兼容所有浏览器内容区必须有透明背景否则水印被完全遮死覆盖层加让路水印层置顶通过 JS 临时隐藏或转发事件视觉上最满水印不会被任何内容遮挡实现复杂有闪烁风险需要处理边界情况我的实际选择是以背景图平铺为主覆盖层为补充。原因很直白一个不需要兼容处理就能在所有浏览器里表现一致的方案长期维护成本最低。覆盖层方案我会保留但只用在页面结构不允许改动的死角场景里比如第三方嵌入的 iframe 或者由后端拼装的老页面。2. 方案取舍我为什么最后选了背景图铺而不是盖一层很多人第一反应是水印当然要盖在最上面因为在设计语境里水印就是最后一层印刷。但网页不是印刷品网页的层自带事件语义每加一层就多一层拦截风险。把水印从图层降级成底色等于从根上绕过了整个事件模型——它不再是一个可以被点到的对象。2.1 覆盖层方案的先天缺陷覆盖层方案的本质是用一个绝对定位的div铺满视口z-index拉到最高里面放平铺的水印纹理。这个div一旦进入 DOM就会参与命中测试鼠标落在它身上时事件不会继续往下传递。这是浏览器的默认行为不是 bug。于是你会遇到一连串连锁反应输入框点不进去光标不出现按钮的 hover 状态不触发用户以为按钮是灰的文本选不中复制粘贴功能形同虚设下拉框虽然视觉上穿透了但点击落到水印层上展不开选项。更隐蔽的是mousedown和click的分离——某些情况下mousedown被水印吃掉click却落到了目标元素上导致拖拽排序、双击编辑这类依赖完整事件序列的交互彻底坏掉。我遇到过一个典型案例一个带行内编辑的表格页加了覆盖层水印之后单元格双击进不去编辑态。排查了两小时才发现是dblclick的两次mousedown都被水印截走了。这类问题用肉眼几乎看不出来只能靠事件监听器打日志。2.2 pointer-events: none 在各版本浏览器里的真实支持情况现代做法很干净给水印层加一行.wm-layer { position: absolute; left: 0; top: 0; right: 0; bottom: 0; z-index: 9999; background: url(./img/wm-tile.png) repeat; pointer-events: none; opacity: .12; }pointer-events: none的含义是这个元素是鼠标命中的透明体事件会直接穿透到它下面的元素。这是 SVG 时代就有的属性后来被引入 CSSFirefox 3.6、Safari 4、Chrome 16 都支持。但 IE 家族到 IE10 都没有实现它IE11 才补上。浏览器pointer-events: none用于 HTML 元素IE10 及以下不支持IE11支持Edge 全系支持Firefox 3.6支持Chrome 16 / Safari 4支持所以只要目标环境里有 IE10 及以下的用户这行 CSS 就是无效声明浏览器会直接忽略它水印层依然是那个吃事件的盖子。这时候必须准备第二套方案。还有一点需要提醒即使在支持pointer-events: none的浏览器上水印层的视觉遮挡依然存在。半透明层盖在文字上会把文字的整体对比度往下拉长时间阅读会累。这是纯视觉问题事件穿透解决不了它只能靠降低水印浓度或者改成背景方案。2.3 背景图平铺方案的代价与应对把水印做成背景最大的好处是它从 DOM 层面就不存在了——没有元素就没有事件就没有兼容问题。IE7 对背景图的平铺支持非常完善background-repeat: repeat从 IE4 就有了PNG 的 alpha 通道从 IE7 开始支持所以水印素材可以直接用一张带透明度的 PNG不需要任何滤镜兜底。代价有两个。第一背景会被子元素的背景色遮挡。页面里只要有一个白底的卡片、一个灰底的表头水印在那些区域就消失了视觉上会出现水印断层。第二背景图默认不打印如果业务要求打印件也带水印需要在打印媒体里另做处理。第一点的应对办法是逐容器叠加给每一个有背景色的容器都加上同一张水印背景图位置用background-position对齐让视觉上连成一片。这听起来笨但非常有效而且因为是背景完全不影响这些容器内部控件的行为。第二点后面单独说。3. 落地实现一平铺水印背景这条路线我用了很多次稳定性最好。关键在于水印素材怎么做、CSS 怎么写、以及怎么处理那些不听话的白色区块。3.1 水印素材怎么做才能无缝平铺水印平铺最容易翻车的地方是接缝。如果素材的边缘没有处理好平铺之后会出现明显的网格线看起来像页面裂了。做法是在一个固定尺寸的画布上把水印文字旋转后居中放置四周留出足够的安全边距保证文字的任何部分都不触碰到画布边缘。尺寸怎么定假设水印文案是内部资料 严禁外传八个字字号 14px旋转角度 -25 度。文字本身的宽度约 112px高度约 14px。旋转之后的包围盒宽度是112 × cos25° 14 × sin25° ≈ 101 6 107px高度是112 × sin25° 14 × cos25° ≈ 47 13 60px。也就是说单个水印的视觉占位大约是 107×60。平铺单元要在这个基础上加间距。间距太小水印糊成一片太重间距太大又显得稀疏防护感不足。我的经验值是水平和垂直各留 60px 到 80px 的呼吸空间所以最终单元取240×160比较舒服密度适中在 1366×768 的老屏幕上大约能排下 5×4 个水印视觉上满而不挤。导出时注意两点。第一格式必须是 PNG-24 带 alpha不要用 PNG-8索引色在渐变和抗锯齿上会出现锯齿边。第二水印本身的灰度要浅。我一般用#999999或者更浅的#AAAAAA再配合整体的filter: alpha(opacity12)最终呈现的浓度大约在 5% 到 8% 之间。这个浓度下正常阅读完全不受影响但拍照时能被清晰看到正好卡在需求的平衡点上。提示不要在图层面板里直接把文字透明度拉到 10% 再导出那样 PNG 自带 alpha后续没法再用 CSS 统一调整浓度。正确做法是导出不透明的浅灰文字浓度交给 CSS 控制改起来只动一个数值。3.2 容器结构与 IE7 的 CSS 写法最省事的挂载点是body。浏览器有一个特性如果html元素没有设置背景body的背景会传播到整个画布覆盖完整的滚动区域页面再长也不会断。这个行为在 IE7 里同样成立所以把水印背景直接加在body上一行 CSS 就能铺满全站。html { /* 关键html 不能有背景否则 body 背景不再传播 */ background: transparent; } body { margin: 0; padding: 0; background-color: #ffffff; background-image: url(./img/wm-tile.png); background-repeat: repeat; background-position: 0 0; }如果页面结构里有一个主容器比如#app需要单独控制背景也可以挂在那里但要保证这个容器的高度能撑满内容区。IE7 里height: 100%的百分比高度依赖父级有确定高度链路一断就失效所以更稳的做法是不设高度让它被内容自然撑开背景跟着容器走。这里有个细节需要注意IE7 下如果给body加了filter或者zoom之类的属性会触发 hasLayout可能导致背景传播行为异常。所以在body上尽量只放背景声明不要叠加其他怪异属性。3.3 背景会被白色区块盖住怎么一层层补回去实际页面里body背景往往只能露出一部分。表格有表头底色、卡片有白色背景、弹窗有独立面板、侧边栏有分割色块这些都会把水印盖掉。视觉上就出现了水印在有的地方有、有的地方没有的割裂感。解决思路是给每个有背景色的容器补上同一张水印图并且用background-position让它们对齐。这里有个技巧如果所有容器都从0 0开始平铺由于各自的定位起点不同接缝处会出现错位。要真正无缝需要让背景相对于同一个原点计算。IE7 不支持background-origin和background-clip这些现代属性所以只能靠固定偏移量来对齐。我的做法是在构建时给每个容器算好偏移如果某个卡片距离页面左上角是(x, y)就把它的background-position设为-x px -y px。这样从视觉上所有容器的水印图案都来自同一张无限大的底图。.card { background-color: #ffffff; background-image: url(./img/wm-tile.png); background-repeat: repeat; /* 让水印图案与页面原点对齐 */ background-position: -24px -180px; }这个偏移量需要根据实际布局算写死是可以接受的因为后台系统的布局通常稳定。如果布局是流式的、位置会变那就退一步不强求对齐改成把水印只在最外层画一次然后要求所有子容器背景透明。后一种做法对页面样式的侵入更大需要逐个检查background声明但一劳永逸。注意给容器补水印背景时不要用background简写一次性覆盖容易把原有的background-color冲掉。分开写background-color和background-image改动范围可控。3.4 如果连 IE6 也要扛VML 平铺的写法虽然这次的需求只写到 IE7但内网项目经常在最后关头冒出某某终端还是 IE6的追加需求提前知道兜底方案能省一轮返工。IE6 不支持 PNG 的 alpha 通道透明区域会渲染成灰色块所以必须用滤镜AlphaImageLoader或者 VML。AlphaImageLoader有个致命限制它的sizingMethod只有crop、image、scale三种全都不支持平铺。想平铺只能用 VML 的v:fill把type设成tile。html xmlns:vurn:schemas-microsoft-com:vml head style v\:* { behavior: url(#default#VML); } .wm-vml { position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: -1; } /style /head body v:rect classwm-vml fillcolornone strokedfalse v:fill typetile src./img/wm-tile.gif opacity0.08 / /v:rect /body /html注意素材要换成 GIF 或者不透明的 PNG因为 IE6 处理不了 alpha。这个方案我只在确实有 IE6 需求时用VML 的渲染性能和调试体验都很差能不碰就不碰。3.5 水印视觉强度调校透明度与对比度的平衡水印浓度是个需要现场调的事纸上算不准。我一般准备三档alpha(opacity8)、alpha(opacity12)、alpha(opacity18)在真实页面上截图对比。判断标准是把页面缩放到 100%正常阅读正文如果视线不会被水印吸引走说明浓度合格如果感觉文字像蒙了一层灰说明过了。反过来把截图缩小到 30%如果水印还能辨认说明防护强度够如果完全看不见那就太淡了拍张照稍微一压缩就没了。还有一个容易被忽略的点水印的颜色比浓度更影响阅读体验。同样是 10% 的浓度纯黑水印会让页面发脏浅灰水印则几乎无感。所以素材本身就要用浅灰不要用黑色再靠透明度去稀释。/* IE7 及以下的浓度控制 */ .wm-alpha { filter: progid:DXImageTransform.Microsoft.Alpha(opacity12); -ms-filter: progid:DXImageTransform.Microsoft.Alpha(opacity12); zoom: 1; /* 触发 hasLayout保证滤镜生效 */ } /* 现代浏览器的浓度控制 */ .wm-alpha { opacity: .12; }zoom: 1这行在 IE7 里几乎是必备的因为滤镜需要元素有 hasLayout 才会生效。但要注意加了zoom之后元素可能产生意料之外的布局变化需要实测确认。4. 落地实现二覆盖层 事件让路兜底有些页面改不动比如由后端模板拼装、或者嵌在别人系统里的 iframe你没法给每个容器补背景。这时候只能回到覆盖层方案用 JS 补上 IE7 缺失的那部分能力。4.1 什么时候必须用覆盖层三种情况我会选择覆盖层一是页面结构完全不由我控制无法逐容器加背景二是水印需要显示在弹窗、浮层之上而背景方案天然在底层三是水印内容需要动态生成比如带上当前时间和访问标识用背景图就得走接口返回图片成本太高。第三种情况其实很常见。如果水印里要带当前用户标识 时间戳这类动态信息纯静态背景图就不够用了。这时可以退一步用一张透明底 动态文字的方式——把水印层做成一个绝对定位的容器里面放一个重复排列的文字网格容器本身不接收事件。但在 IE7 里容器不接收事件这件事做不到只能靠 JS。这就是所谓的让路。4.2 让路方案点下去那一瞬间把水印藏起来思路很朴素用户按下鼠标的瞬间把水印层设为不可见等点击流程走完再恢复。因为水印层不可见时不参与命中测试mouseup、click、focus都会自然落到它下面的元素上整个事件序列完整无缺。(function () { var layer document.getElementById(wmLayer); if (!layer) { return; } // 只在老 IE 上挂载现代浏览器靠 pointer-events 就够了 if (!document.attachEvent) { return; } var restoreTimer null; var isHidden false; function hideLayer() { if (isHidden) { return; } isHidden true; layer.style.visibility hidden; // 兜底万一 mouseup 丢失1 秒后强制恢复 if (restoreTimer) { clearTimeout(restoreTimer); } restoreTimer setTimeout(showLayer, 1000); } function showLayer() { if (!isHidden) { return; } isHidden false; layer.style.visibility visible; if (restoreTimer) { clearTimeout(restoreTimer); restoreTimer null; } } layer.attachEvent(onmousedown, function () { hideLayer(); }); document.attachEvent(onmouseup, function () { // 延后一拍保证 click 事件能正常派发 setTimeout(showLayer, 0); }); })();这里有几个点值得展开。第一为什么用visibility: hidden而不是display: nonedisplay: none会让元素脱离渲染树触发重排虽然水印层是绝对定位不影响其他元素的位置但重排的代价在 IE7 上还是偏高。visibility: hidden只是不绘制不影响布局恢复也更快。第二为什么mouseup要绑在document上因为水印层已经隐藏了mouseup不会再落在它身上必须从文档级监听才能捕获到。第三setTimeout(showLayer, 0)这行很重要如果同步恢复click事件的派发可能发生在水印层已经重新可见之后导致点击又被吞掉。延后一拍能让click先完成。这个方案的副作用是水印会在点击瞬间闪一下。实践下来如果恢复延迟控制在 50ms 以内肉眼基本感知不到因为人眼对短暂消失的容忍度比想象中高。但在滚动 点击的复合操作里偶尔会看到轻微的闪烁属于可接受范围。4.3 用 elementFromPoint 做精确命中转发有些场景不适合藏起来再恢复比如拖拽选择文本。这时可以用另一种思路水印层老老实实接着事件但接收到之后主动转交给它下面的元素。IE 提供了document.elementFromPoint(x, y)传入视口坐标返回该点最上层的元素。问题在于它返回的往往就是水印层自己所以要先把水印层临时设为不可见取到目标元素再立刻恢复。function hitTest(x, y, layer) { var oldVisibility layer.style.visibility; layer.style.visibility hidden; var target document.elementFromPoint(x, y); layer.style.visibility oldVisibility || visible; return target; }拿到目标元素之后用fireEvent把事件转发过去。function forwardEvent(target, type, nativeEvent) { if (!target || !target.fireEvent) { return; } var cloned document.createEventObject(nativeEvent); target.fireEvent(on type, cloned); }这里必须提醒一句fireEvent只能触发绑定在元素上的事件处理器不会触发浏览器的默认行为。也就是说你转发一个onclick给a标签它的onclick处理器会执行但页面不会跳转转发给input不会自动聚焦转发给select下拉框不会展开。所以对不同类型的元素要做特判function activate(target) { if (!target) { return; } var tag (target.tagName || ).toUpperCase(); if (tag A target.href) { window.location.href target.href; } else if (tag INPUT || tag TEXTAREA) { target.focus(); } else if (tag LABEL) { var id target.getAttribute(for); var ctrl id document.getElementById(id); if (ctrl) { ctrl.focus(); } } }select是个死胡同——IE7 里没有任何脚本手段能主动展开一个下拉框。遇到这种情况只能靠让路方案在mousedown时把水印藏起来让原生点击自己去展开。综合来看elementFromPoint这套组合更适合处理简单的点击和聚焦不适合替代完整的事件模型。我的做法是默认用让路方案只有当页面存在大量依赖坐标命中的自定义组件比如自绘的树形控件、拖拽面板时才叠加 elementFromPoint 做补充。4.4 滚动跟随IE7 下别指望 position: fixed水印要铺满整个视口如果页面很长水印层的高度必须跟内容高度一致否则滚到底部就没了。做法是给水印层设position: absolute高度取document.body.scrollHeight在窗口resize和内容变化时重算。function syncHeight(layer) { var h Math.max( document.documentElement.scrollHeight || 0, document.body.scrollHeight || 0, document.documentElement.clientHeight || 0 ); layer.style.height h px; }如果你希望水印相对视口固定滚动时水印不动IE7 标准模式虽然支持position: fixed但配合滤镜或者页面里有其他 hasLayout 元素时会出现重绘残影、滚动抖动的问题。我的选择是老老实实用 JS 模拟。function stickToViewport(layer) { var pending false; function reposition() { if (pending) { return; } pending true; setTimeout(function () { var top document.documentElement.scrollTop || document.body.scrollTop || 0; var left document.documentElement.scrollLeft || document.body.scrollLeft || 0; layer.style.top top px; layer.style.left left px; pending false; }, 30); } if (window.attachEvent) { window.attachEvent(onscroll, reposition); window.attachEvent(onresize, reposition); } reposition(); }30ms 的节流很关键。IE7 的滚动事件触发频率极高一次滚动能触发几十上百次不节流的话每一帧都要读写样式页面会直接卡死。这个数字是我反复试出来的低于 20ms 效果不明显高于 50ms 会出现明显的水印滞后。提示绝对定位层铺满时用left/right/top/bottom四向拉伸比width: 100%; height: 100%更稳。IE7 里width: 100%在出现纵向滚动条时容易撑出横向滚动条四向拉伸不会。4.5 水印被删掉怎么办轻量守护水印的防护价值取决于它是否容易被移除。任何一个会用开发者工具的人删掉一个div是几秒钟的事。虽然这不是技术能彻底解决的问题但加一层轻量检测能提高门槛。function guard(layer) { setInterval(function () { var alive document.getElementById(layer.id); if (!alive) { // 被移除重新挂回去 document.body.appendChild(layer); return; } // 被改成透明或者隐藏 if (alive.style.display none || alive.style.visibility hidden) { alive.style.display ; alive.style.visibility visible; } }, 2000); }2 秒的间隔是个平衡点。太频繁会影响页面性能太稀疏又起不到作用。但这招只能防住随手删防不住有备而来的绕过这一点在需求沟通时就要说清楚避免给出不切实际的承诺。5. 踩坑记录与排查速查表下面这些现象我在不同项目里至少各遇到过一次整理成表方便对照。现象真实成因处置方式输入框点不进去光标不出现水印层拦截了mousedown改用背景方案或加水印让路脚本按钮 hover 无反应水印层吃掉了mouseover同上检查水印层z-index是否过高下拉框展开后水印出现破洞IE 的select是窗口级控件永远置顶无法通过样式解决只能把水印层避开select区域水印在页面底部消失水印层高度只取了一屏用scrollHeight同步高度滚动时水印明显滞后滚动事件未节流加 30ms 节流水印区域颜色发灰、文字发闷半透明层叠在内容上拉低对比度降低水印浓度素材改用浅灰弹窗打开后水印消失水印层z-index低于弹窗提升水印层z-index或把水印放在弹窗容器内打印出来没有水印浏览器默认不打印背景图打印媒体下改用img标签插入水印IE7 下opacity完全无效IE7 不识别该属性换用filter: alpha(opacityxx)加了滤镜后z-index失效filter触发了新的层叠上下文把滤镜加在内部子元素上不放在定位容器上5.1 打印场景的补充处理后台系统经常要求打印出来的文件也带水印。背景图默认不打印是浏览器的通用行为用户可以在打印设置里勾选背景图形但不能指望他们勾。所以打印媒体里要换一种实现用img标签把水印图片平铺出来。media print { .wm-layer { display: block; position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: 9999; } .wm-layer img { position: absolute; opacity: .1; } }position: fixed在打印媒体里是安全的因为打印不涉及滚动。每张img的位置需要根据 A4 纸张尺寸算好页边距按毫米换算成像素1mm 约等于 3.78px。这部分比较繁琐但一次做完可以复用。5.2 几个容易忽略的细节第一水印层的 DOM 节点不要放在需要被序列化的容器里。有些老系统会把整个页面innerHTML存进隐藏域做快照水印层混进去会导致数据暴涨。我一般把水印挂在body的直接子节点并且加一个明确的id序列化前先剔除。第二attachEvent绑定的事件处理器里this指向window而不是绑定元素和addEventListener的行为不一致。写兼容代码时如果依赖this必须手动绑定否则会静默出错——这个坑极其隐蔽因为控制台不报错只是逻辑不生效。第三IE7 下document.body和document.documentElement获取滚动位置、可视高度时经常对不上必须两边都取再比大小。这个判断我封装成了一个工具函数用在所有需要读视口尺寸的地方。function viewportSize() { var d document; var de d.documentElement; var b d.body; return { width: Math.max(de.clientWidth || 0, b.clientWidth || 0), height: Math.max(de.clientHeight || 0, b.clientHeight || 0), scrollTop: de.scrollTop || b.scrollTop || 0, scrollLeft: de.scrollLeft || b.scrollLeft || 0 }; }6. 一点个人经验折腾过几轮之后我现在的默认选择是能上背景就上背景实在不行才上覆盖层。背景方案的代码量大约只有覆盖层的三分之一出错率低一个数量级而且不需要为了兼容旧浏览器去写一堆特判。很多人排斥背景方案是因为水印会被白色区块遮住这件事需要手动补但补的过程其实是一次页面样式梳理顺带能发现不少冗余的背景声明。真正需要覆盖层的场景我一般只保留两处一个是全屏弹窗的上层因为弹窗的z-index通常很高背景方案盖不住另一个是需要动态文字水印的时候用elementFromPoint配合让路脚本。这两处的代码我都会单独抽成一个模块注释写清楚仅 IE7 兼容路径等哪天老终端下线了删掉这个文件就干净了。最后一个实用小技巧调试水印遮挡问题时先把水印浓度