動作の仕組み的にも構造的にも微妙だとは思いますが、形にはなったのでメモ的に。
なお、表示は重いかもしれません。
実現したいこと
実現したいことは以下の通り。
- スクロールに連動して足跡を付ける
- jQueryは使わない
当ブログのデザインリニューアルの際にやってみたかったことの一つで、jsの練習も兼ねて試作した次第です。
IEではチェックしておらず、それ以外のブラウザに関しても動作の保証はできません。
また、スマホサイズでは邪魔にしかならなそうなので、当ブログではjsの読み込み自体をさせておらず、こちらも未チェックです。
コード(jsのみ)
別ページの「JavaScriptでスクロールに連動した足跡をつける(試作版):メモ」に記載している試作コードを経て、現在当サイトで実装しているコードが以下のものです。
足跡の動作自体は別ページの試作コード2(Lodash使用)の方が良いのですが、jsライブラリは使わない方向でもある程度のものができたため、この案を採用しています。
//上下のスクロールを判別するための基準の初期化
var nowPosition;
//足跡削除用にIDへ連番を付けるため
var count = 1;
//上向き用の連番で、偶数奇数で左右を判別
var countA = 1;
//下向き用の連番で、偶数奇数で左右を判別
var countU = 1;
//足跡削除用の連番
var deleatId = 1;
//スクロールしたら実行するコールバックで足跡設置
var callback = function () {
//現在のスクロール量を計測
var y = window.pageYOffset;
//基準値と現在値の差
diffPosition = y - nowPosition;
//現在の位置を更新
nowPosition = y;
//上向き用の連番で、偶数奇数で左右を判別設定
if(countA % 2 == 0){
idNameA = '-even';
} else {
idNameA = '-odd';
}
//下向き用の連番で、偶数奇数で左右を判別設定
if(countU % 2 == 0){
idNameU = '-even';
} else {
idNameU = '-odd';
}
//足跡を左右に散らす指定
var rand = Math.floor( Math.random() * 11 );
if( (diffPosition > 0)){
//下向きスクロールの時に実行
var foot = document.createElement('div');
foot.className = 'walk-under' + idNameU;
foot.id = 'foot' + count;
foot.style.top = 300 + y + 'px';
foot.style.marginRight = rand + 'px';
var objBody = document.getElementsByTagName("body")[0];
objBody.appendChild(foot);
countU++;
} else {
//上向きスクロールの時に実行
var foot = document.createElement('div');
foot.className = 'walk-above' + idNameA;
foot.id = 'foot' + count;
foot.style.top = 300 + y + 'px';
foot.style.marginRight = rand + 'px';
var objBody = document.getElementsByTagName("body")[0];
objBody.appendChild(foot);
countA++;
}
//処理開始時刻を削除用の関数を呼び出し
footsDeleatTimer();
count++;
};
//スクロール中の動作の間引き処理。設定時間(今は200ms)ごとに関数を実行する目的
window.addEventListener('scroll', throttle(callback, 200));
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}
//足跡削除する時間を指定
var footsDeleatTimer = function(){
setTimeout("footsDeleat()",2000);
};
//削除用関数
function footsDeleat(){
var objBody = document.getElementsByTagName("body")[0];
//足跡追加の連番。実行回数と連動して追加された足跡ごとに削除を行う
var elements = document.getElementById("foot"+deleatId);
if (elements !== null){
objBody.removeChild(elements);
deleatId++;
}
}
cssは以下の通り。
div[class*="walk"]{
position:absolute;
height: 20px;
width: 12px;
top:0;
z-index: 100;
background-repeat: no-repeat;
-webkit-background-size: 12px 20px;
background-size: 12px 20px;
opacity:0;
box-sizing: border-box;
-webkit-animation: vanish 2s;
animation: vanish 2s;
background-position: center top;
}
div[class*="odd"]{
background-image: url(images/footprint-left.svg);
right:50px;
}
div[class*="even"]{
background-image: url(images/footprint-right.svg);
right:30px;
}
div[class*="under-odd"]{
-webkit-transform: scaleY(-1);
transform: scaleY(-1);
}
div[class*="under-even"]{
-webkit-transform: scaleY(-1);
transform: scaleY(-1);
}
@-webkit-keyframes vanish{
0%{
opacity: 0;
}
20%{
opacity: .8;
}
60%{
opacity: .8;
}
100%{
opacity: 0;
}
}
@keyframes vanish{
0%{
opacity: 0;
}
20%{
opacity: .8;
}
60%{
opacity: .8;
}
100%{
opacity: 0;
}
}
上記のcss内の下記の部分で左右の足の間の基本間隔を設定しています。
div[class*="odd"]{
background-image: url(images/footprint-left.svg);
right:50px;
}
div[class*="even"]{
background-image: url(images/footprint-right.svg);
right:30px;
}
実際にはjsで設定している乱数が影響するため、足跡画像の横幅を加味しつつ、左右の足が近くなりすぎないように設定しています。
離れすぎるとガニ股になり、近すぎると酔っぱらった千鳥足のようになるため、実際の動作を見ながら設定するほうがよいと思います。
足跡に関しての補足
上下に動く左右の足跡を作るにはどうすれば良いか悩みましたが、以下の形を基本として構築してみました。
- スクロールイベントが発生した際に、一定の時間で関数を実行
- スクロールの上下を感知して追加する要素を変更
- 上下ごとに、追加順で偶数奇数を区別して要素を変更
2と3はそれなりにうまくいった気がしますが、動作を見た限りでは、1の時間を用いた仕組みを改善する必要性を感じています。
足跡画像のポイント
足跡の画像としてsvgを使っていますが、もちろんsvgである必要はありません。
cssをより利用する前提であれば、一つの足跡画像で4つ分が作れるでしょうし、この辺りはお好みの形でという部分です。
なお、自然な足跡に見えるように動作させるためには、以下の点がポイントになるかと思います。
- スクロールの上下でつま先の向き(歩く方向)を変える
- 右足と左足のつま先はそれぞれ外側に傾斜していて違いがある
結果として以下のものが必要になります。
- 「上向き左足」「上向き右足」「下向き左足」「下向き右足」の4つを作る必要がある
今回はコードにあるようにclassとidを半ば無理矢理用いて4つの状態を作成し、cssなどを反映させるために特定できる手がかりを作りましたが、もっとスマートで無駄の無い方法があるかもしれません。
足跡の削除
足跡を消す仕組みに関しては、cssでフェードアウトさせた後に一定秒数で要素ごと削除しています。
この削除の方法もなかなか良い方法が思いつかず、左右や上向き下向きのすべてを通しで連番にした数値をつけ、順に消す形に落ち着きました。
足跡を追加と同時に、一定時間経過後にremoveChildで消せば良いだけと簡単に考えていましたが、ここもなかなか実現できなかったためです。
試した結果、以下の方法でそれなりに意図した動作が作れました。
- 足跡を追加したタイミングでsetTimeoutを使用
- setTimeoutで削除用関数を一定時間後に実行
- 削除用関数には通しの連番を順にセットする
- 足跡追加からsetTimeoutで指定したタイミングで削除実行
表示が重くなる原因としてはイベントの数自体も問題になるでしょうが、足跡が短時間で増え続けた場合の負荷も問題になっているようでした。
表示する足跡の最大数を制限することも考えましたが、単純な個数制限ではスクロール速度によっては足跡が途切れる可能性が高く、それでは実装する意味があまりないように思えました。
そこで、足跡は表示後2秒で消す、程度の速さにしています。
参考サイト
以下のサイトなどを参考にさせていただきました。
- ライブラリを使わない素のJavaScriptでDOM操作
- javascriptでタグのidを文字列と変数で構成する方法を教えてください。
- javascriptでスクロールの上下を検知
- JavaScriptで要素の位置座標を取得する方法
- JavaScriptでランダムの数(乱数)を作る方法
- カッコいいけど遅すぎる! JSのスクロールイベントを改善するベストな方法
- JavaScriptプログラミング講座【ウェブパフォーマンスについて】
- JS:setInterval()やsetTimeout()で指定する関数に引数を渡す
- Quick Tip: How to Throttle Scroll Events
結び
コードを見るとかなり不可解なことをしているように見えるかもしれませんが、この辺りが試行錯誤の跡ということで…。
こうして形にしてみたものの、仕組み自体が手探りのせいもありコードの無理矢理感も強く、改善の余地はいろいろありそうです。
理想とする動作は映画「ハリー・ポッターとアズカバンの囚人」のエンディング(だったと思うのですが)で見た地図のような状態でしたが、そのレベルの実現方法は見えていません。
9人がこの記事を評価
役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。
連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。
4本足は行けますか?
改修すれば実現できるとは思いますが、表現的な部分の再現(自然に見える動きの再現)は大変かなと思います。
表現が厳しいんですね。
分かりましたありがとうございます!