V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
woniuppp
V2EX  ›  程序员

有意思的面试题-如何在网页置灰的前提下,保持部分元素彩色

  •  
  •   woniuppp ·
    shengxinjing · 2022-12-06 23:19:20 +08:00 · 1853 次点击
    这是一个创建于 709 天前的主题,其中的信息可能已经有所发展或是发生改变。

    网页置灰方案讨论

    如何在网页置灰的前提下,保持部分元素彩色

    在线体验

    本文视频版链接

    最近哀悼日,网页端如何一键变灰已经有很多实现方式了,但是我看到一个推文很有意思,是一个不错的面试题

    现在网页置灰已经不仅仅是一行 css 的事了,如何在网页置灰的前提下,部分元素保持彩色,这是一个不错的 system design 题

    欢迎加我,畅聊前端

    一键变灰

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

    html{
        filter: grayscale(100%);
    }
    

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

    html{
      -webkit-filter: grayscale(100%);
      -moz-filter: grayscale(100%);
      -ms-filter: grayscale(100%);
      -o-filter: grayscale(100%);
      filter: grayscale(100%);
      filter: gray;
      filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
    }
    

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

    <button class="btn" id="set-gray">置灰</button>
    
    let style = document.createElement('style')
    let graySelector = 'gray-filter'
    style.setAttribute('type', 'text/css')
    // style.setAttribute('data-vite-dev-id', id)
    style.textContent = `.${graySelector}{
      -webkit-filter: grayscale(100%);
      -moz-filter: grayscale(100%);
      -ms-filter: grayscale(100%);
      -o-filter: grayscale(100%);
      filter: grayscale(100%);
      filter: gray;
      filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
    }`
    document.head.appendChild(style)
    
    let root = document.querySelector('html')
    let btn = document.querySelector('#set-gray')
    btn && btn.addEventListener('click', () => {
      setAllGray()
    }, false)
    
    function toggleClassName(el,name){
      if (el.className.indexOf(name) > -1) {
        el.className = el.className.replace(name, '').trim()
      } else {
        el.className = [el.className, name].join(' ')
      }
    }
    
    function setAllGray() {
      toggleClassName(root,graySelector)
    }
    
    

    这样可以在后端通过接口的形式决定是不是加载这段 js 就可以了

    01.gif

    那么问题来了,如何在置灰的前提下部分元素保持彩色呢

    filter 重置(失败)

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

    html{
        filter:grayscale(100%);
    }
    .not-gray{
        filter:none;
    }
    

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

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

    遮挡解决方案 backdrop-filter

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

    html {
        position: relative;
        width: 100%;
        height: 100%;
    }
    html::before {
        content: "";
        position: fixed;
        backdrop-filter: grayscale(100%);
        pointer-events: none;
        inset: 0;
        z-index: 100;
    }
    

    还可以把遮罩的 position 换成 absolute, 实现一个只置灰首屏的效果,不过我感觉没啥必要

    02.gif

    然后我们可以设置指定元素的 z-index ,超过 backdrop-filter 的 100 就可以, 就有首屏+部分彩色的效果

    05.gif

    .not-gray{
      position: relative;
      z-index:1000;
    }
    

    元素遍历标记

    backdrop-filter 其实也有他的兼容性问题,尤其是 firefox 版本 102(最新 107)之前都不能用,filter 方案更普及一些,不过作为面试题的话 我们还可以继续用 filter 这个方法,

    image.png

    image.png

    我们设置有一些选择器保持彩色,然后统计出当前这个网页中,需要置灰的元素,网页是一个属性结果,我们先对选中元素的父元素进行遍历标记

    06.jpeg

    
    let body = document.body
    //配置选择器,命中这个列表选择器的不置灰
    let selectors = ['#not-gray2', '.not-gray3']
    selectors.forEach(selector=>{
        let doms = [...document.querySelectorAll(selector)].forEach(v=>{
          if(!v) return 
          v.staycolor = true
          let parent = v.parentNode
          while(parent && !parent.colorful){
            parent.colorful = true
            parent = parent.parentNode
          }
        })
    })
    
    

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

    let graySelector = 'gray-filter'
    walk(body)
    
    function walk(node){
        if(node.nodeType!==1) return 
        if(node.staycolor) return 
        if(!node.colorful){
          toggleClassName(node,graySelector)
          return
        }
        for (var i = 0; i < node.children.length; i++) {  
          var child = node.children[i]; 
          walk(child)
        }  
    }
    

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

    04.gif

    总结

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

    12 条回复    2022-12-09 23:04:30 +08:00
    chrisqin
        1
    chrisqin  
       2022-12-06 23:25:21 +08:00 via iPhone   ❤️ 4
    总结来说为了皇上不死无所不用其极。
    me221
        2
    me221  
       2022-12-06 23:53:02 +08:00   ❤️ 1
    为了某人, 前端难度一下子就提升了
    efaun
        3
    efaun  
       2022-12-07 00:49:55 +08:00
    title 里包含"刁"字, 浏览器会自动恢复为彩色
    m1klos
        4
    m1klos  
       2022-12-07 08:57:52 +08:00
    独彩者
    wangnimabenma
        5
    wangnimabenma  
       2022-12-07 09:52:11 +08:00
    挺好的,再灰一次?
    llzzll1234
        6
    llzzll1234  
       2022-12-07 09:52:47 +08:00   ❤️ 2
    本来好好的技术讨论贴,楼上几个非要秀一下存在感
    kyuuseiryuu
        7
    kyuuseiryuu  
       2022-12-07 11:37:40 +08:00   ❤️ 1
    《如何阉割太监能避免临床感染》

    —— 本来好好的学术帖
    Junh
        8
    Junh  
       2022-12-07 11:38:25 +08:00
    露出鸡脚了,小黑子
    lookStupiToForce
        9
    lookStupiToForce  
       2022-12-07 14:33:31 +08:00
    小鸡子露出黑脚了,明天出庭记得戴上 v2ex
    MMMMMMMMMMMMMMMM
        10
    MMMMMMMMMMMMMMMM  
       2022-12-07 23:11:17 +08:00
    你很会蹭热点,可惜并没有出现你预想中的点击量和热议效果
    woniuppp
        11
    woniuppp  
    OP
       2022-12-08 18:06:39 +08:00
    @MMMMMMMMMMMMMMMM 谢谢夸奖,确实是个不错的面试题
    cppgohan
        12
    cppgohan  
       2022-12-09 23:04:30 +08:00
    是个不错的面试题, chatgpt 好像还不会做, 它给到的答案类似
    html{
    filter:grayscale(100%);
    }
    .not-gray{
    filter:none;
    }
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1032 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 22:04 · PVG 06:04 · LAX 14:04 · JFK 17:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.