概要
- 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を書いておくより、別メソッドとして独立させておいた方が、今回のケースのような中断条件を追加しやすく、読みやすいコードになる。