
記事のコードについて
この記事の目的は、くしゃみごとに既成のプラグインや同様のソリューションに頼ることなく、人々にさまざまな自明でない効果を実装するように教えることです。 デモのコード(および記事自体)は、すべての可能なチュートリアルでいっぱいになった多くの不要なものを使用せずに、簡単でわかりやすい方法で書かれています(願っています)。 ES6、webpack、react、モジュラーシステムなどはありません。 切望されているHello Worldを最終的に画面に表示するために、コマンドラインに5〜15分間座って環境のさまざまな依存関係をインストールする必要はありません。 少なくとも一度JavaScriptを使用したことがあるほとんどすべての人が理解できるため、「不器用な」コードを作成し、jQueryを使用します。最終的に、触れることができるものを取得します。
さまざまなライブラリとプリプロセッサが数回クリックするだけで接続されるコードペンですべてのデモを作成し、コードを書くだけで済みます。
背景
それはすべて、インターネット上で美しいサイトjetlag.photosにつまずいたという事実から始まりました。 すぐに「フードの下」を覗かずにクローンを実現したいという要望がありました。 後で判明したように、必要な場合でも、すべてがキャンバスに実装されていたため、devToolsから有用な情報を抽出できませんでした。 そして、圧縮され、凝集されたキャンバスコードを掘り下げるのは、今でも不正です。 そこで、標準的なhtml + css + jsの組み合わせを使用して、画面に表示されるものだけをガイドとしてデモを見始めました(とにかく実際にはcanvasとは友達ではありません)。
始める
最初に、内部に背景を持つブロックを含むフルスクリーンコンテナを作成する必要があります。
<div class="ws-pages"> <div class="ws-bgs"> <div class="ws-bg"></div> <div class="ws-bg"></div> <div class="ws-bg"></div> <div class="ws-bg"></div> <div class="ws-bg"></div> </div> </div>
すべての背景は追加のブロックで囲まれています。テキスト用のブロックが残っているためです。 次にスタイルを設定します。
.ws { &-pages { overflow: hidden; position: relative; height: 100vh; // 100% } &-bgs { position: relative; height: 100%; } &-bg { height: 100%; background-size: cover; background-position: center center; // , , &:after { content: ""; display: table; clear: both; } }
CSSについて
既にお気付きのように、プリプロセッサを使用しています。 この場合、SCSS構文のSASSです。
次に、背景の一部を作成し、適切なブロックとスタイルに追加する必要があります。 私たちの仕事は、すべてのピースが単一のブロックのように見えるようにすることです。そのブロックの背景はbackground-size:cover and centeredを使用して引き伸ばされます。 これを行うには、背景画像が設定される各ブロック内に追加のブロックが追加されます。 各室内ユニットは画面幅の100%を占有し、各ステップで左にシフトするため、最終的に全体像が得られます。
.ws-bg { &__part { overflow: hidden; // position: relative; float: left; // height: 100%; cursor: grab; user-select: none; // &-inner { position: absolute; top: 0; // left js width: 100vw; // 100% height: 100%; background-size: cover; background-position: center center; } } }
var $wsPages = $(".ws-pages"); var bgParts = 24; // 24 . var $parts; function initBgs() { var arr = []; var partW = 100 / bgParts; // , % for (var i = 1; i <= bgParts; i++) { var $part = $('<div class="ws-bg__part">'); // var $inner = $('<div class="ws-bg__part-inner">'); // var innerLeft = 100 / bgParts * (1 - i); // $inner.css("left", innerLeft + "vw"); $part.append($inner); $part.addClass("ws-bg__part-" + i).width(partW + "%"); // arr.push($part); } $(".ws-bg").append(arr); // $wsPages.addClass("s--ready"); // $parts = $(".ws-bg__part"); }; initBgs();
当初の実装方法
最初は、.ws-bg__partの後に使用し、sassのループ内の擬似要素の左側を設定しました。 しかし、それからjsとsassの可変数の部品を同期する必要があるのは悪い習慣であり、すべてをjsで完全に実行することにしました。
そのため、関数の最後にs-readyクラスをコンテナに追加します。 これは、最初に背景とともに表示される.ws-bgブロックから背景を削除して、javascriptがパーツを追加する前にユーザーにコンテンツが表示されるようにするために必要です。 パーツを追加した後、メインブロックは移動しないため、メインブロックの背景は必要ありません。 .ws-bgに次を追加します。
.ws-bg { .ws-pages.s--ready & { // .ws-pages.s--ready .ws-bg background: none; } } // .ws-bg .ws-bg__part-inner c css // , css ,
マウスを動かす
マウスでスワイプのハンドラーを接続し、ページ切り替えを実装するときが来ました。
var curPage = 1; // var numOfPages = $(".ws-bg").length; // // var winW = $(window).width(); var winH = $(window).height(); // , $(window).on("resize", function() { winW = $(window).width(); winH = $(window).height(); }); var startY = 0; var deltaY = 0; $(document).on("mousedown", ".ws-bg__part", function(e) { // startY = e.pageY; // Y deltaY = 0; // $(document).on("mousemove", mousemoveHandler); // $(document).on("mouseup", swipeEndHandler); // }); var mousemoveHandler = function(e) { var y = e.pageY; // X , var x = e.pageX; index = Math.ceil(x / winW * bgParts); deltaY = y - startY; // moveParts(deltaY, index); // }; var swipeEndHandler = function() { // / $(document).off("mousemove", mousemoveHandler); $(document).off("mouseup", swipeEndHandler); if (!deltaY) return; // Y , // " " , if (deltaY / winH >= 0.5) navigateUp(); if (deltaY / winH <= -0.5) navigateDown(); // // , changePages(); }; // function navigateUp() { if (curPage > 1) curPage--; }; function navigateDown() { if (curPage < numOfPages) curPage++; };
変数deltaYおよびindexに基づいて部品を移動する機能を追加します(mousemoveHandler内でmoveParts関数を呼び出します)。 アクティブパーツ(ポインターが配置されているパーツ)のいずれかの側にある各パーツが、スワイプの距離をある程度遅らせてY軸に沿って移動し始めたときに、両面ラダーの効果を作成する必要があります(言葉で説明すると、視覚的に理解するよりもはるかに困難です)。 さらに、パーツがアクティブから遠いほど、その「ステップ高さ」は小さくなります。
叩かないで
私は夜遅くに、私が甘く眠るはずだったときに、部品の機能的な動きを書きました。 そして、私はすべてを賢くリファクタリングするのが面倒だったので、あなたがしなければならないのはこの恐怖を見ることです。 誰かがそれを美しくリファクタリングできるなら、私はコメントを手伝って喜んでいるでしょう。
私はまた、何が起こっているのかを適切に説明することで重大な問題を経験しました。実際、すべてが見た目よりも単純で理解しやすいものであり、今では普通に自分の考えを表現することはできません= /
私はまた、何が起こっているのかを適切に説明することで重大な問題を経験しました。実際、すべてが見た目よりも単純で理解しやすいものであり、今では普通に自分の考えを表現することはできません= /
var staggerVal = 65; // var staggerStep = 4; // , var changeAT = 0.5; // function moveParts(y, index) { // y = deltaY; index - var leftMax = index - 1; // var rightMin = index + 1; // // var stagLeft = 0; var stagRight = 0; // var stagStepL = 0; var stagStepR = 0; var sign = (y > 0) ? -1 : 1; // movePart(".ws-bg__part-" + index, y); // for (var i = leftMax; i > 0; i--) { // " " var step = index - i; // , // var sVal = staggerVal - stagStepL; // 15 , 1 // . . stagStepL += (step <= 15) ? staggerStep : 1; // , if (sVal < 0) sVal = 0; stagLeft += sVal; // var nextY = y + stagLeft * sign; // Y // deltaY , if (Math.abs(y) < Math.abs(stagLeft)) nextY = 0; movePart(".ws-bg__part-" + i, nextY); // } // , for (var j = rightMin; j <= bgParts; j++) { var step = j - index; var sVal = staggerVal - stagStepR; stagStepR += (step <= 15) ? staggerStep : 1; if (sVal < 0) sVal = 0; stagRight += sVal; var nextY = y + stagRight * sign; if (Math.abs(y) < Math.abs(stagRight)) nextY = 0; movePart(".ws-bg__part-" + j, nextY); } }; function movePart($part, y) { var y = y - (curPage - 1) * winH; // Y // ( ) // : // TweenMax.to(%%, % %, {% %} // Back easing bounce. bounce TweenLite.to($part, changeAT, {y: y, ease: Back.easeOut.config(4)}); };
既にお気付きのように、アニメーションにはGSAP(greensock)を使用しています。 私はアニメーション用の特別なライブラリなしでほとんどのデモを行うのに慣れていますが、それは非常に楽しくて素晴らしいので、今回はリクエストに応じて自転車を実装するのに時間がかかることに気付いて自分自身を残念に思うことにしました。 。 これは、リアルタイムアニメーションが必要であるためです。リアルタイムアニメーションは、スムーズさを維持しながら、前のアニメーションを自動的に一時停止し、移動すると次のアニメーションを開始します。
ここで、ページ変更関数を作成する必要があります。 ここではすべてが単純であり、基本的にmoveParts関数の非常に軽量なバージョンを使用しています。
var waveStagger = 0.013; // 13 // , - , function changePages() { var y = (curPage - 1) * winH * -1; // var leftMax = index - 1; var rightMin = index + 1; TweenLite.to(".ws-bg__part-" + index, changeAT, {y: y}); for (var i = leftMax; i > 0; i--) { var d = (index - i) * waveStagger; // , TweenLite.to(".ws-bg__part-" + i, changeAT - d, {y: y, delay: d}); } for (var j = rightMin; j <= bgParts; j++) { var d = (j - index) * waveStagger; TweenLite.to(".ws-bg__part-" + j, changeAT - d, {y: y, delay: d}); } }; // . // debounce. $(window).on("resize", function() { winW = $(window).width(); winH = $(window).height(); changePages(); });
代替案
遅延ステップを使用したアニメーションが必要ない場合は、関数を2行のコードに減らすことができます。 しかし、私は美学の観点から遅延オプションがより好きでした。
これで、スワイプマウスで背景を移動できます。 デモの中間バージョンはこちらにあります 。
ウェーブスクロール
次に、デモを1ページスクロールの本格的な実装に変えるために、マウスホイールと矢印にハンドラーをアタッチします。 まあ、それと同時に、なぜそのような名前が選ばれたのかが明らかになるでしょう(それを思いついたのは私ではありませんでしたが、彼らは私を助けてくれました)。
// , var waveBlocked = false; var waveStartDelay = 0.2; // // . DOMMouseScroll FireFox $(document).on("mousewheel DOMMouseScroll", function(e) { if (waveBlocked) return; if (e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0) { navigateWaveUp(); } else { navigateWaveDown(); } }); $(document).on("keydown", function(e) { if (waveBlocked) return; if (e.which === 38) { navigateWaveUp(); } else if (e.which === 40) { navigateWaveDown(); } }); function navigateWaveUp() { // navigate , // , , - if (curPage === 1) return; curPage--; waveChange(); }; function navigateWaveDown() { if (curPage === numOfPages) return; curPage++; waveChange(); }; function waveChange() { waveBlocked = true; // var y = (curPage - 1) * winH * -1; for (var i = 1; i <= bgParts; i++) { // , var d = (i - 1) * waveStagger + waveStartDelay; TweenLite.to(".ws-bg__part-" + i, changeAT, {y: y, delay: d}); } var delay = (changeAT + waveStagger * (bgParts - 1)) * 1000; // setTimeout(function() { waveBlocked = false; // // onComplete gsap'e, }, delay); };
すべての基本機能が実装されています。
わずかな最適化
このデモを電話で見た後、スワイプ中にパフォーマンスに関する重大な問題に遭遇しました。 その後、モーションハンドラー(mousemoveまたはtouchmove)を少し最適化する必要があることをすぐに思い出しました。 このために、rAFでもあるrequestAnimationFrameを使用します。
rAF? WTF?
// , , rAF , window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback){ window.setTimeout(callback, 1000 / 60); }; })(); // throttle , function rafThrottle(fn) { // var busy = false; // , return function() { // , if (busy) return; // , busy = true; // fn.apply(this, arguments); // // rAF, , requestAnimFrame(function() { busy = false; }); }; }; // mousemove rafThrottle var mousemoveHandler = rafThrottle(function(e) { // });
この最適化により、Nexus 5のパフォーマンスが大幅に向上しました。これは、マウス/指を絶えず動かして、アニメーションの計算と再起動の回数を減らしたためです。 はい、デスクトップでも、すべてがうまく機能しているように見えました(ブラウザが統合ビデオカードで動作することを考えると、以前は問題はありませんでしたが)。
テキストヘッダーの実装方法は説明しません。そこではすべてが非常に簡単で、コードを読むことで役に立ちます。
それは基本的にそれです! デモリンク:
エディター付きバージョン。
iframeとcodepenの断片のないフルスクリーンバージョン -電話で確認してからこのバージョンを開く場合、他のデバイスはタッチデバイスでバグがあります。
それでは、タッチデバイスのコードについてはどうでしょうか。
元のデモでは、タッチデバイスのサポートが実装されています。 実際には、追加のtouchmoveハンドラーのみが追加されます。これは同じことを行い、見つけやすいコード行をさらにいくつか追加します。 しかし、最適化した後でも、キャンバス上で実行されている元のサイトのパフォーマンス(少なくともNexus 5上)でデモが大幅に低下するため、この部分については記事に記載しないことにしました。 ただし、これはそのような効果にとっては良い結果です。
また、モバイルデバイスでは、移動中に背景部分の端に黒いバーが表示されるという問題があります。 これは、移動中の幅(%)と3D変換の組み合わせによるものです。これにより、パーツのサイズがわずかに変化し、身体の黒い背景の一部が見えるようになります。
また、モバイルデバイスでは、移動中に背景部分の端に黒いバーが表示されるという問題があります。 これは、移動中の幅(%)と3D変換の組み合わせによるものです。これにより、パーツのサイズがわずかに変化し、身体の黒い背景の一部が見えるようになります。
最後に
これは私の最初の記事なので、品質は非常に不十分です。 そして、デモは明らかに最高のものではありません。 しかし、このデモについて書くことにしました。たった10日前に作成し、自分の考えをよく覚えているからです。