通过 JavaScript 检测 :first-letter

4

我需要知道一个元素是否使用了:first-letter样式,而且这个解决方案应该是通用的,不依赖于类名或特殊的样式属性。有没有什么方法?例如:

<p class="initial">First</p>
<p>Second</p>

.initial:first-letter {
float: left;
font-size: 1.5em;
font-weight: bold;
}

$('p').click(function(){
    // how to determine if :first-letter is applied to current paragraph?

});

2
为什么不直接检查段落是否应用了.initial类?如果应用了,那么你就知道:first-letter伪元素样式已经应用了 - 如果有第一个字母的话(并且浏览器支持:first-letter)。 - crush
因为这应该是一个通用的解决方案,用作Chrome中的一个错误的变通办法,并且必须在一个框架中使用,所以我不知道样式的名称。 - Sebastian Baltes
1
我很想了解Chrome的bug。你有Chrome bug报告票据的链接或者可以演示它的jsfiddle吗?除了你正在尝试的方法,还有其他的解决方法吗?另外,如果这是一个已知的bug,在下一个版本中他们有可能修复它吗?(你是否检查过Canary版本或夜间版本中是否仍存在该bug?) - Spudley
可能有一种方法,可以使用jQuery的选择器功能进行比较。听起来很丑陋,但你可能是对的,没有其他方法了。 - Sebastian Baltes
我通过chrome://feedback功能发送了一个错误报告,但没有收到任何回复。作为证明:http://jsfiddle.net/c66kk/1/ 在那里你可以看到window.getSelection报告了错误的索引值,参考我的另一个问题http://stackoverflow.com/questions/17952375/bug-in-chrome-with-window-getselection-and-first-letter-any-workaround - Sebastian Baltes
谢谢您提供上下文,这很有帮助(顺便说一句,我已经为您的原始问题点赞了)。 - Spudley
3个回答

3
如果您的CSS是自托管的,您可以:
  1. 获取所有CSS块的列表
  2. 过滤掉在块的选择器中不包含:first-letter的CSS块
  3. 迭代剩余CSS块的列表,并以目标元素作为接收者,以当前CSS块的选择器作为参数运行matchesSelector()
    1. 如果matchesSelector()返回true,则当前CSS块的规则会影响目标元素的第一个字母。
    2. 否则,继续进行列表中的下一个CSS块
如果CSS不是自托管的且CDN未发送CORS标头,则由于隐私问题,无法读取CSS文件源,并且无法执行此操作。
我还从算法中省略了规则级联的解决方法。另一个问题是找出伪类选择器如何以错误的方式影响 matchesSelector
例如,请考虑:
p.webkitMatchesSelector("p:first-letter") //false

因此,在尝试匹配之前,必须从字符串中删除伪类,例如:first-letter,因为它们是不相关的(但像:nth-child这样的伪类则不然,因为它们确实会影响匹配)。
演示http://jsfiddle.net/PBuzb/5/(请记住,我提到的问题在这里处理得不太好)(代码的基础原本由Crush撰写)。
为什么在跨域情况下不允许读取CSS源代码?
您只能从其他域名显示图像、运行css/js等,但绝不能以任何方式访问其数据的原因是隐私。
最容易解释的案例是图片。假设我已登录facebook,而facebook使用如http://facebook.com/myprofile.png这样的URL来访问私人照片。现在我去了evil.com,而evil.com可以加载该图像,因为我已经登录到facebook,facebook将给他们该图像。通常情况下,他们无法访问图像或以任何方式窃取它,但如果我们启用了跨域数据访问,他们现在可以访问我的私人照片并传播它们出去。
CSS中也可能发生同样的情况,facebook服务器生成的特定于用户的CSS可能包含我私人朋友的用户ID。如果任何我可以访问的网站都可以链接到该CSS并开始阅读它,那么它们将不再是那么私密。
是的,主要问题在于浏览器在跨域请求中发送cookie的方式,如果浏览器在从evil.com请求facebook图像/CSS时没有发送cookie,则facebook无法响应特定于用户的CSS或我的私人照片,因为cookie是必需的以识别我。
现在想象一下,如果浏览器不发送cookie。evil.com仍然可以通过这种方式访问任何内部数据,因为我的浏览器可以访问内部网。能够在evil.com网站上显示http://intranet/Myboss.jpg作为图像不是问题,但evil.com能够读取图像数据,从而能够复制和传播它是一个问题。

1
@crush 哈哈,是的,我提到了你:P 写代码获取所有css块很麻烦,如果没有你,我就不会做出这个演示,所以谢谢 :) - Esailija
document.styleSheets对于没有CORS标头的非自托管CSS文件不起作用,因为我认为浏览器已经加载和解析了它们,所以不应该存在安全问题? - Sebastian Baltes
@SebastianBaltes 隐私问题很难在评论中解释。我可以写一个 Paste Bin。 - Esailija
@SebastianBaltes的pastebin对我无法访问,所以我在这里解释一下:http://jsfiddle.net/M9bdH/ - Esailija
@Esailija 只需修改您的答案以包括解释-这将使您的答案更完整。 - crush

2
你可以遍历cssRules,构建一个包含所有::first-letter规则的数组,然后检查元素是否有这些规则。 演示DEMO(仅在Chrome中测试通过)
(function() {
    var firstLetterRules = [];

    var loadRules = function() {
        var stylesheets = document.styleSheets;

        for (var i = 0; i < stylesheets.length; i++) {
            var stylesheet = stylesheets[i];
            var rules = stylesheet.cssRules;

            for (var k = 0; k < rules.length; k++) {
                var rule = rules[k];

                if (rule.selectorText.indexOf("::first-letter") !== -1) {
                    //It is a ::first-letter selector. Add the rule to a list of ::first-letter rules.
                    firstLetterRules.push(rule.selectorText.toUpperCase());
                }
            }
        }
    };

    window.hasFirstLetterStyle = function(element) {
        var fullSelector = element.nodeName;

        if (element.className != '')
            fullSelector += '.' + element.className;

        fullSelector = fullSelector.toUpperCase() + "::FIRST-LETTER";

        for (var i = 0; i < firstLetterRules.length; i++) {
            if (fullSelector == firstLetterRules[i])
                return true;
        }

        return false;
    };

    loadRules();
})();

var element = document.getElementById("init");

if (hasFirstLetterStyle(element)) {
    console.log("Element has a first-letter style rule.");
}

可能会有一些情况无法捕捉到。目前,它只匹配完全限定的CSS规则。我将尝试修改它,使其更加容错。例如,它目前无法匹配* :: first-letter规则。它也无法匹配.initial :: first-letter。它假设规则将指定标记名称。我将在第二个修复这个问题。 - crush

1

你不能这样做。

伪类及其样式在JS代码中无法访问。

最接近的方法是检查元素是否具有相关类别,这些类别会触发first-letter样式,并在JS代码中假设如果存在该类别,则first-letter样式必须已应用。

除了手动解析整个CSS文件以外,确实没有其他方法。

这也适用于其他伪类和伪元素(您会在此处找到许多类似的问题,询问关于:before:after的类似问题,并得到类似的答案)。


1
你不需要手动解析它,可以直接使用document.styleSheets。但实际上这只是最简单的一步。 - Esailija
好吧,也许不一定要进行完整的解析过程,但您需要遍历每个选择器,找到其中带有:first-letter选择器的那些,并确定您给定的元素是否与其中任何一个匹配。这听起来是可行的,但如果您有很多CSS(并且如果您有多个应用于同一元素的first-letter样式,则会变得非常混乱),则可能会很快变得混乱。 - Spudley
找出元素是否与给定的选择器文本匹配只需调用 matchesSelector,难点在于处理边角情况和选择器规则文本中不出现 * 等特殊符号。 :P - Esailija

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接