旧版 Safari(≤15.3)中 :placeholder-shown 伪类失效,因 WebKit 实现不完整:输入后不及时退出、聚焦后仍匹配或完全忽略;需用 JS 监听 input/focus/blur 事件,通过 value.trim()===“”判断并切换 class。

:placeholder-shown 在旧版 Safari(≤15.3)中不触发或存在渲染延迟,不能靠加前缀或提高权重解决,必须用 JavaScript 检测 value 状态并手动切换 class。
为什么旧版 Safari 的 :placeholder-shown 会“失效”
旧版 Safari(尤其是 iOS 15.3 及更早)对 :placeholder-shown 的实现不完整:它可能在首次渲染时正确匹配,但输入后不及时退出,或聚焦后 placeholder 消失却仍保留伪类;部分机型甚至完全忽略该伪类。这不是 CSS 写法问题,而是 WebKit 内核未严格遵循规范所致。Autoprefixer 对它加前缀无效——因为 Safari 原生就用 :placeholder-shown,不存在厂商前缀变体。
用 JS 判断占位符是否“应显示”的核心逻辑
关键不是监听“有没有 placeholder 属性”,而是判断“当前是否应该显示占位符”,即语义上的空值状态。这必须基于 input.value.trim() === "",而非 input.value === "" 或 !input.value。
- 空格、全角空格、零宽字符(
\u200b)、粘贴进来的不可见字符都会让value非空但语义为空,trim()是必要过滤 - 避免用
propertychange或input+compositionend组合——Safari 14–15.3 对 composition 事件支持混乱,容易漏掉中文输入法上屏后的状态更新 - 必须监听
input、focus、blur三个事件,且初始加载后立即执行一次判断(防止 SSR 渲染后状态不同步)
兼容旧版 Safari 的最小可行 JS 实现
以下代码在 Safari 13.1–15.3 中稳定运行,不依赖框架,也避开 React/Vue 的受控组件陷阱(如初始 value="" 导致 DOM 值非空):
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
立即学习“前端免费学习笔记(深入)”;
const input = document.querySelector('input[placeholder]');
const updateState = () => {
const isEmpty = input.value.trim() === '';
input.classList.toggle('placeholder-shown', isEmpty);
input.classList.toggle('has-value', !isEmpty);
};
<p>input.addEventListener('input', updateState);
input.addEventListener('focus', updateState);
input.addEventListener('blur', updateState);
updateState(); // 初始同步注意:input 元素必须带 placeholder 属性(哪怕只是 placeholder=" "),否则 Safari 会跳过占位符渲染逻辑,JS 也无法模拟语义。
和 CSS 样式配合时的常见坑
别把 JS 切换的 class 和原生伪类混用在同一选择器里,比如 input.placeholder-shown, input:placeholder-shown —— 旧版 Safari 解析到不认识的伪类会整条规则丢弃,导致 class 版本也失效。建议彻底分离:
- CSS 中只用 class 驱动样式:
input.placeholder-shown { border-color: #ddd; } - 移除所有
:placeholder-shown相关 CSS 规则,哪怕写了也加/* safari-fallback */注释提醒自己禁用 - 如果还要支持现代浏览器纯 CSS 方案,用
@supports selector(:placeholder-shown)包裹,确保旧 Safari 完全跳过
真正难处理的不是“怎么写 JS”,而是当输入框被框架接管(如 Vue 的 v-model 绑定 "")、或用户通过自动填充插入不可见字符时,DOM 值和视觉状态脱节——这时候连 trim() 都不够,得额外检查 input.selectionStart === 0 && input.selectionEnd === 0 并结合 getComputedStyle 查占位符是否可见,但那已超出轻量 fallback 范畴。

















