LoginSignup
2
3

More than 5 years have passed since last update.

スクロール位置に応じたフェード効果付きの表示・非表示の制御

Posted at

概要

  • setTimeoutで実行をずらしていると、意図しない結果になるタイミングがある。
  • 中断条件を入れることでそれを防ぐ。

具体例

※便宜上jQueryを使用。

  • 実行サンプル
  • 200pxスクロールしたら表示する。
  • 一番上までスクロールしたら消えている。
HTML
<div class="wrap"></div>
CSS
body {
  height: 1000px;
}

.wrap {
  position: fixed;
  background-color: #CCC;
  left: 10px;
  bottom: 0;
  opacity: 0;
  width: 100px;
  height: 100px;
  transition: opacity 0.3s;
}

.wrap.is-show {
  opacity: 1;
}

.wrap.is-hide {
  display: none;
}

とりあえず作成

  • だいたいOKだが、勢い良く一番上までスクロールした時に消えてくれない場合がある。
class Sample {
  constructor(){
    this.SHOW_CLASS = 'is-show';
    this.HIDE_CLASS = 'is-hide';
    this.THRESHOLD = 200;
    this.$window = $(window);
    this.$wrap = $('.wrap');

    this.bindEvent();
  }
  bindEvent(){
    this.$window.on('scroll', () => this.toggle());

    this.$wrap.on('transitionend', e => this.out(e));
  }
  toggle() {
    const scroll = this.$window.scrollTop();

    if(scroll < this.THRESHOLD){
      this.hide();
      return;
    }

    this.show();
  }
  isShow() {
    return this.$wrap.hasClass(this.SHOW_CLASS);
  }
  show() {
    this.$wrap.removeClass(this.HIDE_CLASS);

    setTimeout(() => this.$wrap.addClass(this.SHOW_CLASS), 100);
  }
  hide() {
    this.$wrap.removeClass(this.SHOW_CLASS);
  }
  out(e) {
    const isOpacity = e.originalEvent.propertyName === 'opacity';

    if(!isOpacity || this.isShow()){
       return;
    }

    this.$wrap.addClass(this.HIDE_CLASS);
  }
};

new Sample();

消えない理由

  • 一番上までスクロールしたときに下記の状態になっている。
<div class="wrap is-show"></div>

表示上は消えているが、下記のような状態になるケースも。

<div class="wrap is-show is-hide"></div>

setTimeoutの問題

上に向かって勢い良くスクロールした場合、

200px以下の位置にいる時にshowメソッドが実行され、setTimeoutの待機状態に入る。

setTimeout(() => this.$wrap.addClass(this.SHOW_CLASS), 100);

その後、200pxより上に来た際にhideメソッドが実行され、is-showクラスが外れる。

this.$wrap.removeClass(this.SHOW_CLASS);

待機していたsetTimeoutの処理が実行され、is-showクラスが付与される。

this.$wrap.addClass(this.SHOW_CLASS);

このような順番で処理が行われる結果、一番上までスクロールしたはずなのに is-showのクラスが付与されてしまう。

対処法

  • show, hideそれぞれに中断条件を付与する。
  • is-showクラスの有無で判定する。

完成コード

JS
class Sample {
  constructor(){
    this.SHOW_CLASS = 'is-show';
    this.HIDE_CLASS = 'is-hide';
    this.THRESHOLD = 200;
    this.$window = $(window);
    this.$wrap = $('.wrap');

    this.bindEvent();
  }
  bindEvent(){
    this.$window.on('scroll', () => this.toggle());

    this.$wrap.on('transitionend', e => this.out(e));
  }
  toggle() {
    const scroll = this.$window.scrollTop(),
          isShow = this.isShow();

    if(scroll < this.THRESHOLD){
      this.hide(isShow);
      return;
    }

    this.show(isShow);
  }
  isShow() {
    return this.$wrap.hasClass(this.SHOW_CLASS);
  }
  show(isShow) {
  // 既に表示されている場合は処理中断
    if(isShow){
       return;
    }
    this.$wrap.removeClass(this.HIDE_CLASS);

    setTimeout(() => this.$wrap.addClass(this.SHOW_CLASS), 100);
  }
  hide(isShow) {
   // 既に非表示の場合は処理中断
    if(!isShow){
       return;
    }
    this.$wrap.removeClass(this.SHOW_CLASS);
  }
  out(e) {
    const isOpacity = e.originalEvent.propertyName === 'opacity';

    if(!isOpacity || this.isShow()){
       return;
    }

    this.$wrap.addClass(this.HIDE_CLASS);
  }
};

new Sample();

まとめ

  • scrollに関する処理をsetTimeoutでずらすと、処理が前後することがある。
  • 適切に処理を中断・間引きする必要がある。

備考

  • toggleの関数内にaddClass、removeClassを書いておくより、別メソッドとして独立させておいた方が、今回のケースのような中断条件を追加しやすく、読みやすいコードになる。
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3