关于前端置灰

737 阅读3分钟

最近不少国内网站app都置灰了,所以聊一下前端怎么置灰。

置灰主要分三种:全局置灰,首屏置灰和部分置灰。全局置灰比较多;首屏置灰就是第一屏是灰色的,往下滚动又是彩色,比如B站App;在一些场景中国徽或一些重要人物的照片是不能置灰的,因此只能部分置灰。

点击查看效果

全局置灰

首先要知道大部分页面的置灰是通过CSS中的filter属性实现的。通过设置filter: grayscale(100%);可以实现对元素的置灰。为了兼容性,一般这么写:

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

然后通过js给html标签加上.gray

/**
 * 切换全部置灰
 */
function toggleAllGray() {
  const htmlElement = document.documentElement;
  if (htmlElement.classList.contains("global-gray")) {
    htmlElement.classList.remove("global-gray");
  } else {
    htmlElement.classList.add("global-gray");
  }
}

首屏置灰

filter属性存在性能问题,在一些老机子上有滑动卡顿的情况;虽然可以通过transform开启硬件加速,不过如果只置灰首屏也能符合需求的话终究更好。

backdrop-filter属性可以让你为一个元素后面区域添加图形效果(如模糊或颜色偏移)。因为它适用于元素背后的所有元素,为了看到效果,必须使元素或其背景至少部分透明。

思路就是给首屏添加一层遮罩,给该遮罩设置backdrop-filter

.first-gray {
  position: relative;
  width: 100%;
  height: 100%;
}
.first-gray::before {
  content: "";
  position: absolute;
  top: 0;
  width: 100%;
  height: 100%;
  backdrop-filter: grayscale(100%);
  pointer-events: none;
  inset: 0;
  z-index: 100;
}

可以看到上面还加了个pointer-events: none;,原因在于如果叠加了一层遮罩效果在其上,那这层遮罩下方的所有交互时间都将失效,譬如 hover、click 等。我们给pointer-events设置为none,可以让这层遮罩不阻挡事件的点击交互。

部分置灰

一开始的想法是给不能置灰的元素添加属性filter: none;,但是发现不生效。即父元素设置了filter: grayscale(100%);,那么子孙元素怎么设置都无法突破该效果。于是想到用js来手动处理,只对需要置灰的元素置灰。

对于DOM树,首先标记不能置灰的节点,然后从节点到根节点的路径上的所有节点都不能置灰,其余节点可以。于是问题就变成了一道向上遍历树的问题了。

/**
 * 切换部分置灰
 */
function togglePartialGray() {
  const htmlElement = document.documentElement;
  if (htmlElement.classList.contains("partial-gray")) {
    htmlElement.classList.remove("partial-gray");
    return;
  } else {
    htmlElement.classList.add("partial-gray");
  }
  const noGrayElements = document.querySelectorAll(".no-gray");
  // 给从根节点到noGray节点路径上的所有节点添加noGray标记
  for (const el of noGrayElements) {
    // 添加totalNoGray标记,表示在遍历到该节点时不再往下遍历
    el.classList.add("total-no-gray");
    let node = el;
    while (node) {
      node.classList.add("no-gray");
      node = node.parentElement;
    }
  }
  // 从根节点开始往下遍历,对于没做noGray标记的
  dfs(htmlElement);

  /**
   * 递归遍历给节点做标记
   * @param {HTMLElement} node
   * @returns
   */
  function dfs(node) {
    if (!node || node.classList.contains("total-no-gray")) return;
    if (node.classList.contains("no-gray")) {
      for (const child of node.children) {
        dfs(child);
      }
    } else {
      node.classList.add("gray");
    }
  }
}