CSS小发明 :in-viewport

阿里云幸运券

人们用大量的时间去优化网页滚动的流畅度,包括尽量缩短 JS 执行时间,发明 Passive event listener 等。 相同的页面效果,如果不使用 JS 就能实现,那一定是最流畅的。例如下面的优酷世界杯的页面,如果使用 JS 实现,可以监听滚动事件,在滚动事件的回调函数中,计算每个比赛的 element 的位置,判断是否在视口中,并控制相应的明星 element 出现或隐藏。

而如果有一个 CSS 伪类,当元素在视口的时候,就会匹配 CSS 规则,那么就可以使用它代替 JS 方法,很完美地实现这个效果,因此创造了一个 CSS 伪类 :in-viewport。

.popup {
width: 100%;
transform: translate(0,15vw);
transition:transform 200ms;
}
.container:in-viewport .popup {
transform: translate(0,0);
}

其实 :in-viewport 这个概念以前就已经有讨论过,不过是带参数的,而且最终也没有浏览器实现,讨论可见:

https://lists.w3.org/Archives/Public/www-style/2016Jun/0015.html

https://lists.w3.org/Archives/Public/www-style/2016Nov/0116.html 基于 Chromium,我们提出一种具体实现:

A、参照 hover,增加一个 CSS 伪类 :in-viewport。

主要就是增加一个枚举: kPseudoInViewPort,并将字符串「in-viewport 」与新增的枚举做对应。

腾讯云代金券

B、增加对 :in-viewport 的匹配

在计算元素的 CSS 样式的时候,元素会一一匹配 CSS 规则是否匹配,所以增加对 :in-viewport 的匹配判断:

SelectorChecker::CheckPseudoClass(…){

case CSSSelector::kPseudoInViewPort:
if (mode_ == kResolvingStyle) {
element.SetObserveVisibility(true);
if (context.in_rightmost_compound) {
element_style_->SetAffectedByInViewPort();
} else {
element_style_->SetUnique();
element.SetChildrenOrSiblingsAffectedByInViewPort();
}
}
probe::forcePseudoState(&element, CSSSelector::kPseudoInViewPort,
&force_pseudo_state);
if (force_pseudo_state)
return true;
return element.IsInViewPort();


}
上面的代码中主要做了两件事:

1、 element.SetObserveVisibility:给元素添加一个 Observer,观察元素是否在视口中。

2、 return element.IsInViewPort():返回元素是否在视口中,这是由上述 Observer 设置的。

C、观察元素是否在视口中

这步也即 SetObserveVisibility 的实现:

void Element::SetObserveVisibility(bool observe){
if (observe == !!visibility_observer_)
return;

if(observe){
visibility_observer_ = new ElementVisibilityObserver(
this, WTF::Bind(&ContainerNode::SetInView,
WrapWeakPersistent(this)));
visibility_observer_->Start(1.0);
}
else{
visibility_observer_->Stop();
visibility_observer_.Clear();
}
}
ElementVisibilityObserver 是 Chromium 中用于方便观察元素是否可见的类,通过构造函数中注册的回调函数,回调元素是否可见。

在这里,注册的回调函数是 ContainerNode::SetInView :

void ContainerNode::SetInView(bool in_view) {
if (in_view == IsInViewPort())
return;

Node::SetInView(in_view);

const ComputedStyle* style = GetComputedStyle();
if (!style || style->AffectedByInViewPort()) {
StyleChangeType change_type = kLocalStyleChange;
if (style && style->HasPseudoStyle(kPseudoIdFirstLetter))
change_type = kSubtreeStyleChange;
SetNeedsStyleRecalc(
change_type,
StyleChangeReasonForTracing::CreateWithExtraData(
StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_in_viewport));
}
if (IsElementNode() && ToElement(this)->ChildrenOrSiblingsAffectedByInViewPort())
ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoInViewPort);
}
这段代码中,也是主要做了两件事:

1、Node::SetInView:给元素设置一个标志,element.IsInViewPort() 根据该标识返回元素是否在视口。

2、SetNeedsStyleRecalc:将元素设置为需要重新计算样式,使得 CSS 样式得以发生改变。

小结
:in-viewport 的实现不算复杂,从上面的例子又可以看到,:in-viewport 确实能比较优雅地实现视口相关的一些页面效果,所以还是期待 CSS 标准中可以增加伪类 :in-viewport 。

腾讯云代金券

原文链接

https://zhuanlan.zhihu.com/p/42290141

服务推荐

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值