Wonderful! WordPress

いまさらのスクロールするとアニメーションで現れる、の独り言

Page No.1

この記事は公開または最後に更新されてから281日が経過しています。情報が古くなっている可能性があるのでご注意下さい。

我が画像サイトを見ていて、ちょっと飽きてきたな~、などと思っていたその頃。
ネットでよく見かける、スクロールするとフワッとアニメーションで現れる効果でも付けてみようかと思ったりして。
だいたいのところこんな事をすれば、と思いつくことなので、全く難しいことでもなさそうだし、すぐにでも適用できそう。
CSStransitionanimation を設定しておいて、javascript scroll イベントで変化させたいプロパティの値を変えてやるか、もしくは、animation を設定したclass をつけたり外したりする、ということでしょう。 それでは、さっそく!ちょっとこのページで試してみますか、と。
たとえばページにこんな要素があると。

<!-- transition で動かすグループ -->
<div class="showcase">
	<div class="posit"><span class="moving leftp">PHP</span></div>
	<div class="posit"><span id="centerp">Word<br>&emsp;Press</span></div>
	<div class="posit"><span id="rightp">MySQL</span></div>
</div>
<!-- animation で動かすグループ -->
<div class="showcase">
	<div class="posit"><span class="moving lefta lwlk">Java<br>script</span></div>
	<div class="posit"><span class="cwlk" id="centera">HTML5<br>CSS3</span></div>
	<div class="posit"><span class="rwlk" id="righta">Python</span></div>
</div>
HTML
CopyExpand

実際に動かすのは、上の部分で言うと'moving'class centerprightp id がついている <span>要素。スクロールイベントによってトリガーとさせるのはclass moving を持つ要素で、他の2つはそれに連動させて動かせば良いという意図です。その親の'posit' class のついている<div>は別にいらないんだけど、狭い画面とか画面の広さの違いに、それぞれ個々に親となるボックスに入っていたほうが、位置決めがフレキシブルにできて簡単になると思うのです。
動かす要素はposition'absolute'にして、他の要素の位置指定に影響を及ぼさず自由に動けるようにし、それ自体の基本的な位置指定は親要素で行うほうが良いだろうという考えなのです。この場合、中身を'absolute'にすると親の中身は空っぽと同じになりぺしゃんこになってしまうので、必要なheight を指定しておく必要がありますね。それさえしておけば、ページ全体のレイアウトに影響を及ぼすことはないので、軽く動かすには必須のことであるという考えなのであります。

@media screen and (min-width:770px){
	.showcase{
		position:relative;
		width:100%;
		height:200px;
		text-align:center;
		margin:20px auto;
		padding:0;
	}

	.posit{
		display:inline-block;
		position:relative;
		width:32%;
		height:200px;
	}

	.moving,#centerp,#rightp{
		display:block;
		position:absolute;
		width:100%;
		top:200px;
		font-size:0em;
		font-weight:bold;
		line-height:0.9em;
		text-shadow:2px 2px 0 white, 6px 3px 0 grey;
		opacity:0;
	}

	.leftp{
		left:500px;
		color:green;
		transition: all 1.5s;
		transition-delay:0.5s;
	}

	#centerp{
		left:0px;
		color:blue;
		transition: all 1.5s;

	}

	#rightp{
		right:500px;
		color:red;
		transition: all 1.5s;
		transition-delay:1.0s;
	}
}

@media screen and (max-width:770px){
	.showcase{
		position:relative;
		width:100wv;
		height:500px;
		text-align:center;
		margin:20px auto;
		padding:0;
	}

	.posit{
		display:block;
		position:relative;
		width:90%;
		height:200px;
		margin:0 auto;
	}

	.moving,#centerp,#rightp{
		display:block;
		position:absolute;
		width:100%;
		top:200px;
		font-size:0em;
		font-weight:bold;
		line-height:0.9em;
		text-shadow:2px 2px 0 white, 6px 3px 0 grey;
		opacity:0;
		transition: all 1.5s
	}

	.leftp{
		left:50px;
		color:green;
		transition-delay:0.3s;
	}

	#centerp{
		left:0px;
		color:blue;
	}

	#rightp{
		right:50px;
		color:red;
		transition-delay:0.6s;
	}
}

.lefta,#centera,#righta{
	display:block;
	position:absolute;
	width:100%;
	top:200px;
	font-size:0em;
	font-weight:bold;
	line-height:0.9em;
	text-shadow:2px 2px 0 white, 6px 3px 0 grey;
	opacity:0;
}

.lefta{
	left:0px;
	color:green;
}

#centera{
	left:0px;
	color:blue;
}

#righta{
	right:0px;
	color:red;
	transition-delay:0.6s;
}

.cwlk{
	animation-name: csway;
	animation-duration:2.0s;
	animation-fill-mode: both;
}
.lwlk{
	animation-name: lsway;
	animation-duration:2.0s;
	animation-fill-mode: both;
	animation-delay: 1.0s;
}
.rwlk{
	animation-name: rsway;
	animation-duration:2.0s;
	animation-fill-mode: both;
	animation-delay: 1.5s;
}

@keyframes csway {
    from{ top: 100px; font-size: 0em; opacity:0; line-height: 0.1; }
    33%{ top: 200px; }
    66%{ top:-50px; }
    to{ top:0px; font-size: 3.0em; opacity:1; line-height: 1.0; }
}

@keyframes lsway {
    from{ left: -100px; top: 200px; font-size: 0em; opacity:0; line-height: 0.1; }
    50%{ left: 100px; font-size:1.0em; }
    to{ left: 0px; top:0px; font-size: 3.0em; opacity:1; line-height: 1.0; }
}

@keyframes rsway {
    from{ right: -100px; top: 200px; font-size: 0em; opacity:0; line-height: 0.1; }
    50%{ right: 100px; font-size:1.0em; }
    to{ right: 0px; top:0px; font-size: 3.0em; opacity:1; line-height: 1.0; }
}
CSS
CopyExpand
PHP
Word
 Press
MySQL

こんな感じでしょうか。これはtransition を使っているもの。
で、これのjavascript はというと。

var moving_list = document.getElementsByClassName( 'moving' ),
	moving_list_len = moving_list.length,
	moving_show_flg = [],
	win_width = window.innerWidth,
	narrow = 0;

// 狭いスクリーン用
if ( win_width < 770 ) {
	narrow = 1;
}

for ( var i = 0; i < moving_list_len; i++ ) {
	// 要素が表示されているか否かのフラグ設定
	// ページ読み込み時においてすべての要素が非表示になっていればこの値は0に設定すべき
	// animation を使用している要素がある場合は、その初期設定をremoveするために1を設定している
	moving_show_flg[ i ] = 1;
}

window.onscroll = function() {
	var targetoffsettop,
		win_height = window.innerHeight,
		// ↓ターゲットの要素が画面下から画面の1/5だけ現れたら発火という値
		from_btm = win_height / 4,
		parent,
		tmpclass,
		tmpclassa,
		appear;

	if ( 0 != moving_list_len ) {
		// ’moving’というクラスをもつ要素全てループ
		for ( var i = 0; i < moving_list_len; i++ ) {
			// この場合は要素の縦位置も変化させているので基準となるその親の縦位置を取得
			parent = moving_list[ i ].parentNode;
			rect = parent.getBoundingClientRect();
			targetoffsettop = rect.top;
			tmpclassa = moving_list[ i ].getAttribute( 'class' );
			tmpclass = tmpclassa.replace ( 'moving ', '' );
			// この値が0よりも小さくなったら出現させる
			appear = targetoffsettop + from_btm - win_height;

			if ( appear < 0 ) {
				if ( 0 === moving_show_flg[ i ] ) {
					var doc = document;

					if ( 'lefta' === tmpclass ) {
						// animation を利用する場合は、設定してあるclassを新たに付加する
						//moving_list[ i ].classList.add( 'lwlk' );
						moving_list[ i ].setAttribute( 'class', tmpclassa + ' ' + 'lwlk' );
						doc.getElementById( 'centera' ).setAttribute( 'class', ' cwlk' );
						doc.getElementById( 'righta' ).setAttribute( 'class', ' rwlk' );
					} else if ( 'leftp' === tmpclass ) {
						// transition を利用する場合は、変化させるものを指定する。
						moving_list[ i ].style.cssText = 'left:0px;top:0px;font-size:3.0em;opacity:1;';
						doc.getElementById( 'centerp' ).style.cssText = 'top:0px;font-size:3.0em;opacity:1;';
						doc.getElementById( 'rightp' ).style.cssText = 'right:0px;top:0px;font-size:3.0em;opacity:1;';
					}
					moving_show_flg[ i ] = 1;
				}
			} else {
				if ( 1 === moving_show_flg[ i ] ) {
					var doc = document;

					if ( -1 !== tmpclass.indexOf( 'lefta' ) ) {
						//moving_list[ i ].classList.remove( 'lwlk' );
						moving_list[ i ].setAttribute( 'class', tmpclassa.replace ( ' lwlk', '' ) );
						doc.getElementById( 'centera' ).setAttribute( 'class', '' );
						doc.getElementById( 'righta' ).setAttribute( 'class', '' );
						console.log ( tmpclass + ': remove class' );
					} else if ( 'leftp' === tmpclass ) {
						// 画面が広い場合と狭い場合で変化させる数値を変えて指定。
						var lp = [ 'left:500px;', 'left:50px;' ],
							rp = [ 'right:500px;', 'right:50px;' ];

						moving_list[ i ].style.cssText = lp[ narrow ] + 'opacity:0;top:200px;font-size:0em;';
						doc.getElementById( 'centerp' ).style.cssText = 'opacity:0;top:200px;font-size:0em;';
						doc.getElementById( 'rightp' ).style.cssText = rp[ narrow ] + 'opacity:0;top:200px;font-size:0em;';
					}
					moving_show_flg[ i ] = 0;
				}
			}
		}
	}
}
JavaScript
CopyExpand

javascript なのでjQuery はなくても動くのですが、でも、このページはもともとjQuery のお世話になっているので、わざわざjavascript で作らなくても、とは思ったりもして。
このjavascript のコードで、window.onscroll で要素を出現させるトリガーとなる条件式においてスクロールの量という値を使ってはいません。39、41行目のところ。これに関しては、その肝となっているgetBoundingClientRect() と共にのちほど詳しく、ということに。
16行目のmoving_show_flg という配列は、動かす各要素がそれぞれ表示されているか否かのフラグを設定してる。別になくてもかまわないものではあるけれど、これがないとスクロールイベントが発生するたびに際限なくスタイルを設定しまくる、ということになってしまう。ちょっとスクロールさせるだけですぐに何百回となってしまう。なんだか無駄なことを常にやらせているようでどうにも気分が悪いのでつけています。

そして、こんどはanimation を使っているもの。animation を使えばより複雑に動かすことができますが、transition のような戻る動きは面倒みてくれない。消える時は、パッと消えてしまいます。当然のこと、区別するためにこちらは少しclass 名を変えています。
なのですが、iPhone6s の実機で試してみたところ、この下のanimation での部分は表示されませんでした。FireFox Chrome のシュミレータではまったく問題なく動いているのですがね・・・、やはりシュミレータなので、ということでしょうか。
class を付けたり取ったりするのは、setAttribute で行う方法と、コメントしてあるclassList を使う方法と両方試してみましたが、どちらもだめでした。setAttribute の方ならできそうな気がしたのですが・・・。ちなみにこれは2020/2/23 においてのこと。
ためしにjQuery でもやってみましたが、結果は同じ。クラスを追加してそのクラスに設定してあるanimation を動かすということはiPhone6s ではできていません。なにかやり方を変えないといけないのかもしれません。でも、たしか、違うページにおいては出来ていたように思うのだけれど・・・、はたしてなにが違うのだろうか・・・?

※後日追記:

後日、他のところで一定時間の間隔で、複数のクラスからその都度選択した一つをつけたりはずしたりの繰り替えしをするということをやったときのこと。
この場合だと、setAttribute だと望むべく結果が得られるものの、classList を使う方法だとあたかも止まってしまっているような状況になってしまった。で、その時々に付加されているクラスをコンソールに書き出して見ると、その理由は一目瞭然のこと理解できた。単純に基本的なところでしたね。classList.add というがごとし。
setAttribute は、既存の付属しているクラス全てにおきかえて、新しく指定されたクラスを設定する。もともとあった付いていたクラスはなくなってしまうので、スタイルなどそのクラスで設定していた場合に、見た目ももとに戻ってしまう、たぶん(試していないので・・・)。であるから、とったりつけたりするクラスはアニメーションで動く設定だけのクラスにして、スタイルはたとえばid の方で設定しておけばということだと。
いっぽう、classList.add の方は、add と言うが如しで既存のクラスはそのまま残してそれに付け加えていく。新しく付けようとしたものが既存であるならその指定は無視されるけれど、既存でないならどんどん増えていくわけ。であるから違う動きをさせようとして、新しく違うクラスを設定しても、既存のクラスもそのまま残っているから期待どうり動いてくれないということが起こる。同じ一つのクラスをつけたり外したりの繰り返しであれば問題ないのだけれど、でもまぁ、その場合はclassList.toggle を使えばよさそう。
前述のように複数のクラスをとっかえひっかえつけてはずしてと言う場合には、setAttribute を使うほうが簡単ということだけれど、問題はつけっぱなしのクラスがある場合かと。まぁ、その場合はどちらでもやりようはありそうです。

Java
script
HTML5
CSS3
Python

で、下はjQuery で同じ処理を書いたもの。

jQuery( function() {
	var win_height = jQuery( window ).height(),
		win_width = jQuery( window ).width(),
		moving_show_flg = [],
		narrow = 0;

	if ( win_width < 770 ) {
		narrow = 1;
	}

	console.log( 'jQ:' + win_width + ' x ' + win_height );

jQuery( '.moving' ).each( function( i, o ) {
	// 要素が表示されているか否かのフラグ
	// ページ読み込み時においてすべての要素が非表示になっていればこの値は0に設定すべき
	// animation を使用している要素がある場合は、その初期設定をremoveするために1を設定している
	moving_show_flg[ i ] = 1;
});

	jQuery( window ).scroll( function() {
		var scrlval = jQuery( window ).scrollTop(),
			// ↓ターゲットの要素が画面下から画面の1/5だけ現れたら発火という値
			from_btm = win_height / 5;

		// ’moving’というクラスをもつ要素全てループ
		jQuery( '.moving' ).each( function( i, o ) {
			// この場合はその親の縦位置を取得
			var imgPos =jQuery( this ).parent().offset().top,
				tmpclassa = jQuery( this ).attr( 'class' ),
				tmpclass = tmpclassa.replace ( 'moving ', '' );

			if ( scrlval > imgPos - win_height + from_btm ) {
				if ( 0 === moving_show_flg[ i ] ) {
					// transition を使用する場合
					if ( 'leftp' === tmpclass ) {
						var appearstr = { top: '0px', fontSize: '3.0em', opacity: '1' };

						jQuery( this ).css( Object.assign( {}, appearstr, { left: '0px' } ) );
						jQuery( '#centerp' ).css( appearstr );
						jQuery( '#rightp' ).css( Object.assign( {}, appearstr, { right: '0px' } ) );
					// animation を使用する場合
					} else if ( 'lefta' === tmpclass ) {
						jQuery( this ).addClass( 'lwlk' );
						jQuery( '#centera' ).addClass( 'cwlk' );
						jQuery( '#righta' ).addClass( 'rwlk' );
					}
					moving_show_flg[ i ] = 1;
				}
			} else {
				if ( 1 === moving_show_flg[ i ] ) {
					if ( 'leftp' === tmpclass ) {
						var lp = [ '500px', '50px' ],
							rp = [ '500px', '50px' ],
							hiddenstr = { top: '200px', fontSize: '0em', opacity: '0' };

						jQuery( this ).css( Object.assign( {}, hiddenstr, { left: lp[ narrow ] } ) );
						jQuery( '#centerp' ).css( hiddenstr );
						jQuery( '#rightp' ).css( Object.assign( {}, hiddenstr, { right: rp[ narrow ] } ) );
					} else if ( -1 !== tmpclass.indexOf( 'lefta' ) ) {
						jQuery( this ).removeClass( 'lwlk' );
						jQuery( '#centera' ).removeClass( 'cwlk' );
						jQuery( '#righta' ).removeClass( 'rwlk' );
					}
					moving_show_flg[ i ] = 0;
				}
			}
		});
	} );
} );
JavaScript
CopyExpand

どちらでもそんなにかわりません、どちらでも動きます。簡素に書こうと思いつつ、Object.assign なんて使ってオブジェクトを結合させてますが、この程度の文字列なら素直に並べて書いたほうがよっぽど速そうではあります・・・。
ただ、プレーンなjavascript とちょっと違うのは、上でも書いてますが、javascript においての条件式の具合。
これは、要素のページ上における位置の取得に使用しているgetBoundingClientRect() から取得できる値によることなのですが。

HTML5
CSS3
Javascript
jQuery
Ajax

《 getBoundingClientRect() 》に関して

jQuery だとスクロールの量とか要素の位置を取得するのは、元々それも目的の1つだったのだと思うのだけれど、ブラウザでの違いを吸収してくれているので、とてもらくちん。いっぽう、javascript だとスクロールの量とか画面の大きさなど、ちょっと前まではそのブラウザによる違いのために、それ用の関数を作ったりしていたわけです。そして、要素の位置というのは、これまであまり使う機会がなかったように思うのですが、その要素の位置というのは何によって取得すればよいのか、ということになります。
と、いうことで探しているとこのgetBoundingClientRect() にたどりついたわけなのです。offsetTop というのも使えそうですが、これは直近の親要素との距離なので、くっついていれば0 のままだし、全くページの上端からの位置とは異なりますから使えません。
さて、このgetBoundingClientRect() なんですが、自分が持っている何冊かの教本には一切出てきません。いったいどのような値を返してくれるのでしょうか。
この語句だけで検索すると一番になっているMDN web docs によれば、「要素の寸法と、そのビューポートに対する位置を返します。」とあります。その後に説明が続くのですが、自分のつるつる脳みそではなんのことやらさっぱり???まぁ、こういうのは実際に値を見たほうが手っ取り早いというもの。
で、上述のjavascript に、次のようなスクロール量を取得する文とコンソール出力の分を加えてみました。

var scrollpos = window.scrollY;

console.log( targetoffsettop + ':' + scrollpos + ':' + ( targetoffsettop + scrollpos ) );
JavaScript
CopyExpand

変数'targetoffsettop'には、動かすべき<span>要素の親である<div>要素のgetBoundingClientRect() で取得できる'top'の値がはいっています。あと、window.scrollY IE ではundefined 。ざんねんです~!window.pageYOffset ならば大丈夫なのでそちらを使用すべきではあるけれど・・・、もういいでしょう~!
このページの上にある方の要素でデータを出しています。

 No. getBounding
ClientRect().top
window.scrollY+ --- 
1399644000 ← ページ読み込み直後、わずかにスクロール
23987134000
33976244000
43964364000
53952484000
63942584000
73935654000
83930704000
93928724000
10107729234000
11104829524000
12102829724000
13101829824000
1499830024000
1598930114000
1697930214000 ← 対象の要素の上端が画面下端にほぼ一致
1794930514000
1892030804000
1991030904000
2088031204000
2121137894000
2216238384000
2312238784000
2411238884000
256339374000
265339474000
273439664000
282439764000
291839824000
30839924000 ← 対象の要素の上端が画面上端にくっつきそう
31-440044000 ← 要素の上端が画面上端から消え始めている
32-1640164000
33-2740274000
34-3640364000
35-4340434000
36-4740474000
37-4840484000

で、見ると合計値がぴったし4000になっていて、これは要素のページにおける上端からの位置がぴったし4000px ということになる。ぴったりの値だと、なんだか意味のある数字のようにも思るのだけれど、全くの偶然の事であって、全く意味はないのです。小数点以下の数値を切り捨てたこともあるし、たまたま偶然に4000という数字になっているだけのことですよ。まぁねっ、偶然にも上の端からほぼ4000px の位置になっていたということではあるのですけれど・・・。だからなにっ?
ちなみにはじめに書いておくと、javascript で出した自分のモニターでの画面(ページが表示されている部分、それをviewport と言うのか)の高さ'window.innerHeight' の値は979 でした。これはちょっと重要な数値。
実際にコンソールを見ながらぐりぐりスクロールをさせていると、すぐに理解できました。
表の16番で対象としている要素の上端が画面の下端からまさに出てくる寸前の状況。この979という値は画面の高さとぴったり一致してます。
そして表の30番、31番あたりで要素の上端が画面の上端にまさにぴったりくっつき、そして画面から消え始めているという状況。ようは要素の上端と画面の上端が一致した時に、この値は0 になるということです。

このgetBoundingClientRect() から得られるtop の値は、ページの、その時画面に表示されている部分の上端から要素の上端までの距離ということになる(要素が画面に入っていなくても)。
ページを読み込んだ直後のように、スクロールが0で、表示されているのがページの上端部分であれば、その値はそのままページの上端から要素の上端の距離ということ。スクロールさせていて、表示させているのがページの中間部分であるなら、その値にスクロール量を足し合わせれば、それがページの上端から要素の上端の距離ということになるわけです。

であるならば、スクロールにおいて出現させる要素の条件判断にスクロールの量は必要ありません。
その要素が画面下端から現れるのは、getBoundingClientRect() top の値が、画面の高さと一致した時です。すなわち、getBoundingClientRect() topから画面の高さを引いた値が0になった時。で、どの辺りで対象の要素を出現させたいかということで、もう少しスクロールさせたら、たとえば画面の下端から画面の1/4ほどのところまで出てきたら出現させたいのであれば、画面の高さx1/4 の値を足しておけばいいということになる。
それが上述のjavascript の33行、35行目の部分となります。
と、いうことでgetBoundingClientRect() の正体がはっきりした。どうも、今ひとつ得体の知れないやつと付き合っているのは気持ちのわるいもので。これにて、すっきり!

・・・

Leave a Reply!

JavaScript is necessary to send a comment.
You can edit and delete your comment if you input a edit key.
Edit key is necessary for attesting you when you edit and delete it.
The tag of HTML cannot be used in comment.
When you comment for the first time, it is displayed after the approval of the administrator.
Because I cannot speak English so much, it takes time to answer.
Required fields are marked *.

※Please enter more than 5 characters only alphabets.
※Edit or delete are possible for 2000 days after approval.

*

♠Simplistic Comment User Editable v4.0

♠When visitors leave comments on the site this site collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.
♠This site does not use cookie when visitors leave comments and commenter edit comment.
♠This site uses Akismet to reduce spam. Learn how your comment data is processed.

Comments feed

Trackback URL : https://strix.main.jp/wp-trackback.php?p=154636

Sanbanse Funabashi

Top

スクロールさせるか画像をクリックすると元に戻ります。