スクロール要素の端を徐々に透過させて続きがある感を演出する

## 概要

スクロール要素の端を徐々に透過させるように見せることで、スクロールできる＝続きがある感を演出してみました。
CSSにて線形グラデーションでの透過は困難なので、白色グラデーション要素をマスクして上下に霧がかかったように効果をつけることで、透過されているように見せています。
スクロールし切った状態ではこの効果は不要のため、スクロール状態に応じて動的にこの効果を切り替えるようにしました。

## 実演

<p class="codepen" data-height="500" data-default-tab="html,result" data-slug-hash="zYeLjQe" data-user="itanium-R" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/itanium-R/pen/zYeLjQe">
  上下の霧かけ</a> by itanium (<a href="https://codepen.io/itanium-R">@itanium-R</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## つくった経緯

MacにてChromeでWebページを閲覧すると、デフォルトでスクロールバーが非表示であり、スクロールしようとしない限り表示されません。
ブラウザのデフォルト挙動を尊重しつつ続きがある感じを演出するため、よく[続きを見る]を押下させるページにて採用されている白色グラデーションの帯を擬似要素で被せる方式を採用しました。

## 実装

### HTML
```
<div class="wrap">
  <div class="fog-added">
    ここに要素の中身を詰め込む。
  </div>
</div>
```

### CSS
``` css
div.wrap {
  display: block;
  position: relative;
}

div.fog-added {
  width: 300px;
  height: 300px;
  overflow-y: scroll;
}

div.fog-added::before {
  content: "";
  width: 100%;
  height: 0px;
  display: block;
  background: linear-gradient(#fff, rgba(255, 255, 255, 0));
  position: absolute;
  top: 0;
  left: 0;
}

div.fog-added::after {
  content: "";
  width: 100%;
  height: 0px;
  display: block;
  background: linear-gradient(rgba(255, 255, 255, 0), #fff);
  position: absolute;
  bottom: 0;
  left: 0;
}
```


### JavaScript
```js
const fogHeight = 50;

/**
 * @param {string} querySelector
 * @returns {CSSStyleRule?}
 */
function getCSSRule(querySelector) {
  for (const sht of document.styleSheets) {
    for (const c of sht.cssRules) {
      if (c.selectorText === querySelector) return c;
    }
  }
}

/**
 * @param {Element} target
 */
function addFog(target) {
  const remainingScrollHeight =
    target.scrollHeight - target.offsetHeight - target.scrollTop;
  const topFogHeight = Math.min(
    target.scrollTop,
    target.offsetHeight / 2,
    fogHeight
  );
  const buttomFogHeight = Math.min(
    remainingScrollHeight,
    target.offsetHeight / 2,
    fogHeight
  );
  getCSSRule("div.fog-added::before").style.height = `${topFogHeight}px`;
  getCSSRule("div.fog-added::after").style.height = `${buttomFogHeight}px`;
}

const fogAddedElm = document.querySelector("div.fog-added");
fogAddedElm.addEventListener("scroll", (e) => addFog(e.target));
addFog(fogAddedElm);
```


## ポイント
- 擬似要素 (before, after) の style は JS で `Element` から直接操作できません。
    - `document.styleSheets` から `CSSStyleRule` を取得し操作することで回避できました。
- 霧がかかったような効果をつける範囲は、以下の条件を満たすように決定されるようにしています。
    - スクロールし切る端に近づけば近づくほど短くし、端では0pxとする
    - 当該要素の外にはみ出さない
    - 標準の大きさを超えない
- CSSの　`background` の `#fff` は配置場所の背景色にあわせることで背景と透過するように演出できます。

## 考慮事項
- `div.fog-added::before`, `div.fog-added::after` が2回定義されるとうまく動きません。 JSの `getCSSRule()` を書き換えれば動くようにできますが、簡単のためこの仕様にしています。
- 今回 `add-fog` を class 指定していますが、このコードでは当該 class が1要素のときのみしか考慮していません。複数使いたい時は追加で手を加える必要があります。

## おわりに
続きがある感じをどうやって表現すればいいかをデザイナーの方と考えた結果、この表現方法ができました。
デザイナーさんには日々感謝感謝です。
チームのメンバーで知恵を絞りあって、より伝わりやすい表現・UI・UXを考えていきたいですね。