CSS

一键变灰

这个大部分同学都写了,直接

  1. html{
  2. filter: grayscale(100%);
  3. }

考虑ie之类的兼容性的话,就直接把兼法容性的属性都搞上去

  1. html{
  2. -webkit-filter: grayscale(100%);
  3. -moz-filter: grayscale(100%);
  4. -ms-filter: grayscale(100%);
  5. -o-filter: grayscale(100%);
  6. filter: grayscale(100%);
  7. filter: gray;
  8. filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
  9. }

如果想控制的更动态一些,可以用js控制html的class来实现这个切换过程

  1. <button class="btn" id="set-gray">置灰</button>
  1. let style = document.createElement('style')
  2. let graySelector = 'gray-filter'
  3. style.setAttribute('type', 'text/css')
  4. // style.setAttribute('data-vite-dev-id', id)
  5. style.textContent = `.${graySelector}{
  6. -webkit-filter: grayscale(100%);
  7. -moz-filter: grayscale(100%);
  8. -ms-filter: grayscale(100%);
  9. -o-filter: grayscale(100%);
  10. filter: grayscale(100%);
  11. filter: gray;
  12. filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
  13. }`
  14. document.head.appendChild(style)
  15. let root = document.querySelector('html')
  16. let btn = document.querySelector('#set-gray')
  17. btn && btn.addEventListener('click', () => {
  18. setAllGray()
  19. }, false)
  20. function toggleClassName(el,name){
  21. if (el.className.indexOf(name) > -1) {
  22. el.className = el.className.replace(name, '').trim()
  23. } else {
  24. el.className = [el.className, name].join(' ')
  25. }
  26. }
  27. function setAllGray() {
  28. toggleClassName(root,graySelector)
  29. }

这样可以在后端通过接口的形式决定是不是加载这段js就可以了
那么问题来了,如何在置灰的前提下部分元素保持彩色呢

filter重置(失败)

如果能直接某个元素重置filter,尝试下面的写法,但是不生效

  1. html{
  2. filter:grayscale(100%);
  3. }
  4. .not-gray{
  5. filter:none;
  6. }

如果filter的算法可逆的话,可以在.not-gray元素上设置一个翻转的filter,查了点资料,Chromium灰色100%的算法如下, 在图像处理方面比较菜,但是看起来全灰的算法不可逆,而且如果在元素上再盖一个canvas也不太好弄放弃

  1. R/G/B = 0.2126R' + 0.7152G' + 0.0722'B

遮挡解决方案 backdrop-filter

有一个解决方案是用backdrop-filter做一个遮罩,毕竟filter还是有点损耗首屏性能的,虽然可以用transform开启硬件优化一些,还可以用遮罩的方式挡住也可以的,并且设置pointer-events: none;不阻挡用户交互,也是一段css搞定

  1. html {
  2. position: relative;
  3. width: 100%;
  4. height: 100%;
  5. }
  6. html::before {
  7. content: "";
  8. position: fixed;
  9. backdrop-filter: grayscale(100%);
  10. pointer-events: none;
  11. inset: 0;
  12. z-index: 100;
  13. }

还可以把遮罩的position换成absolute, 实现一个只置灰首屏的效果,不过感觉没啥必要
然后可以设置指定元素的z-index,超过backdrop-filter的100就可以,就有首屏+部分彩色的效果

  1. .not-gray{
  2. position: relative;
  3. z-index:1000;
  4. }

元素遍历标记

backdrop-filter其实也有他的兼容性问题,尤其是firefox版本102(最新107)之前都不能用,filter方案更普及一些,不过作为面试题的话,还可以继续用filter这个方法,
image.png
image.png
设置有一些选择器保持彩色,然后统计出当前这个网页中,需要置灰的元素,网页是一个属性结果,先对选中元素的父元素进行遍历标记
如何在网页置灰的前提下,保持部分元素彩色 - 图3

  1. let body = document.body
  2. //配置选择器,命中这个列表选择器的不置灰
  3. let selectors = ['#not-gray2', '.not-gray3']
  4. selectors.forEach(selector=>{
  5. let doms = [...document.querySelectorAll(selector)].forEach(v=>{
  6. if(!v) return
  7. v.staycolor = true
  8. let parent = v.parentNode
  9. while(parent && !parent.colorful){
  10. parent.colorful = true
  11. parent = parent.parentNode
  12. }
  13. })
  14. })

然后现在需要置灰的元素都已经标记了colorful,然后遍历一下,递归每个child,如果没有colorful,直接置灰返回,通过递归就可以把所有元素都置灰了

  1. let graySelector = 'gray-filter'
  2. walk(body)
  3. function walk(node){
  4. if(node.nodeType!==1) return
  5. if(node.staycolor) return
  6. if(!node.colorful){
  7. toggleClassName(node,graySelector)
  8. return
  9. }
  10. for (var i = 0; i < node.children.length; i++) {
  11. var child = node.children[i];
  12. walk(child)
  13. }
  14. }

可以把selectors做成从后端读取,就可以动态设置保持彩色的部分了, 不过这样设置filter可能会导致部分元素的定位失效,不过作为面试题的追问还不错。

总结

作为面试题来说,考察了面试者的css,js的dom遍历,递归思想,很不错的入门题。