一次用JS获取placeholder伪元素样式的经历

背景

最近在做一个项目,形式是客户端程序内嵌web页面,我负责的一个页面涉及到qq登录(账号框和密码框),如下图所示

图片

在前期的技术讨论时,因担心使用web的原生输入框会有类似qq密码外泄的安全性问题,这里决定用客户端提供的密码框控件。那么这里在于客户端通信的过程中,web就需要调用客户端提供的一个接口来告知客户端,这个密码框在页面的位置、文字大小、文字色值以便客户端来渲染这个密码框

技术方案

这里实现的方案是,qq账号框用web原生的input元素渲染,而密码框只是用div元素来做一个占位元素,如下所示

图片

然后当页面渲染好后,用JS获取占位元素的样式(比如left、top、宽高、字号、字的颜色等),接着调用客户端接口来将这些样式传递给客户端,类似下面的代码

const passwordDom = document.getElementById('password');
const { left, top, width, height } = passwordDom.getBoundingClientRect();
const { color } = window.getComputedStyle(passwordDom);
// 调用客户端方法
createPassword({
  width,
  height,
  left,
  top,
  color,
  placeholderColor: '...'
})

遇到的问题

这里在获取密码框组件的大部分样式都比较方便,但这里输入框的占位文字并不是使用浏览器的默认样式,而是定制的样式

.form-box-txt {
  width: 100%;
  &::placeholder {
    color: var(--ui-input-placeholdercolor);
  }
}

这里是通过input的placeholder伪元素去设置样式的,问题就在于伪元素虽然可以被浏览器渲染引擎识别并正确渲染,然而「伪元素本身并不是DOM元素,」 所以无法通过js来获取和直接操作,那么这里web要通过什么方式才能拿到这个伪元素的color属性,再传递给客户端?

解决思路

为了解决该问题,我进行了一些搜索并得出也许可行的3种方案

「方案1 直接写死颜色值」

// 调用客户端方法
createPassword({
  width,
  height,
  left,
  top,
  color,
  placeholderColor: 'rgb(148, 148, 148)'
})

这样写确实能生效,也避免了用js去获取样式的逻辑,但有很大的劣势:1. 后续如果视觉走查、产品体验阶段中要求修改这个颜色值,除了修改css外这里还得跟着变 2. 这个组件在后续的多个项目中会用到,如果要考虑组件复用问题,写死的代码是没办法支持业务复用的

「方案2 window.getComputedStyle」 Window.getComputedStyle()方法返回一个CSSStyleDeclaration对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有 CSS 属性的值。直白点说,DOM元素上使用的inline的样式或是通过link标签从外部引入的样式,都可以通过getComputedStyle获取到。

根据MDN的文档所示,getComputedStyle的语法如下:

window.getComputedStyle(element[, pseudoElt]);

对于它的使用方法这里举个例子:

<!-- test.html -->
<style>
.container {
  width: 200px
}
</style>

<body>
  <div class="container"></div>
</body>

<script>
const container = document.querySelector('.container');
const style = window.getComputedStyle(container);
console.log(style.width); // 200px
</script>

我们可以看到14行中通过该方法获取到CSSStyleDeclaration对象后,可以通过样式的key(如color、width)得到对应的value,像这里想获取宽度就通过style.width获取即可。

除了第一个参数要指定DOM元素外,我们还可以传入第二个参数表示指定元素上的哪个伪元素,举个例子:

// test.html
<style>
.container {
  width: 200px
}
.container::after {
  content: "测试文案";
  color: purple;
}
</style>

<body>
  <div class="container">Main</div>
</body>

<script>
const container = document.querySelector('.container');
const pseudoStyle = window.getComputedStyle(container, 'after');
console.log(pseudoStyle.color); // rgb(128, 0, 128)
</script>

如上述示例,我们通过往getComputedStyle加入第2个参数就可以获取到对应伪元素的样式(需要注意的是直接通过样式的key获取颜色值时,返回的是rgb的颜色值)

那么能否通过这种方式获取到placeholder的样式呢?「答案是否定的」,经过我本地的多次测试之后,目前发现这种方案仅适用于这几种伪元素:::before::after::marker::line-marker::first-line::first-letter (其他的我尚未经过本地测试,如果还有别的伪元素支持该方式获取样式的,请列出来)

所以,针对我项目中用到的placeholder伪元素,是无法通过这种方式获取到对应样式的

「方案3 window.getComputedStyle + CSS变量 + getPropertyValue」 前面提到Window.getComputedStyle()方法会返回一个CSSStyleDeclaration对象,而这个对象有一个成员方法getPropertyValue,它也是用来获取某个样式值的,调用该方法并在参数中传入你想要的样式的key,就能拿到对应的value。正常情况下它和直接通过key值获取是一样效果的

// test.html
<style>
.container {
  width: 200px
}
.container::after {
  content: "测试文案";
  color: purple;
}
</style>

<body>
  <div class="container">Main</div>
</body>

<script>
const container = document.querySelector('.container');
const pseudoStyle = window.getComputedStyle(container, 'after');
console.log(pseudoStyle.getPropertyValue('color')); // rgb(128, 0, 128)
</script>

getPropertyValue()和直接使用key访问,都可以访问CSSStyleDeclaration Object。它们两者的区别有:

  • 使用key,则属性的键需要使用「驼峰」写法,如:style.backgroundColor
    使用getPropertyValue(), 不用驼峰,例如:style.getPropertyValue(“border-top-color”)
  • getPropertyValue()方法在IE9+和其他现代浏览器中都支持;在IE6~8中,可以使用getAttribute()方法来代替;

值得一提的是,「getPropertyValue方法有一个特别的地方在于它可以获取CSS变量的值」,而在这个场景下为了该组件以后在多个业务可以复用,我们恰好用的就是CSS变量。

如下所示,在当前场景中我们其实在页面顶层定义了一些CSS变量,其中就包括这个输入框占位文字的变量

/* style.css */
:root {
  --ui-input-placeholdercolor: #999; /* 登录-占位文字色 */
}
.form-box-txt {
  width: 200px;
}
.form-box-txt::placeholder {
  color: var(--ui-input-placeholdercolor);
}

所以针对该场景,我们刚好可以利用getPropertyValue的特性去获取到这个变量对应的值,实现代码如下:

// 1. 先获取DOM节点
const elem = document.querySelector('.container');

// 2. 通过getComputedStyle 获取CSSStyleDeclaration对象
const computedStyle = window.getComputedStyle(elem);

// 3. 根据CSS变量名获取到对应的属性值
const placeholderColor = computedStyle.getPropertyValue('--ui-input-placeholdercolor');
console.log(placeholderColor); // #999

// 4. 调用客户端方法并传入
createPassword({
  // 客户端要的是rgb,加一个colorToRgb方法将十六进制颜色值转换为rgb,这里不做详细讲解
  placeholderColor: colorToRgb(placeholderColor)  
})

这种方案既满足了我们的需求,也不需要在JS中写死颜色值。在样式中只要变量名(–ui-input-placeholdercolor)保持不变,不管颜色值如何变化,我们这段JS代码运行结果都是符合预期的,同时使用CSS变量的方式也方便项目做换肤和组件复用,最终我们项目中就采用了这个方案。作者知识有限,如果各位有更便捷更优雅的方式欢迎指导与补充。

参考文章

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/89165.html

Like (0)
guozi的头像guozi
Previous 2024年6月4日
Next 2024年6月4日

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注