JSX を使用してカルーセル コンポーネントを実装する方法 (フロントエンドのコンポーネント化)

JSX を使用してカルーセル コンポーネントを実装する方法 (フロントエンドのコンポーネント化)

JSX を使用してコンポーネント システムを構築する前に、例を使用してコンポーネントの実装原理とロジックを学習しましょう。ここでは、学習例としてカルーセル コンポーネントを使用します。カルーセルの英語名は「Carousel」で、回転木馬を意味します。

前回の記事「JSX を使用してマークアップ コンポーネント スタイルを確立する」で実装したコードは、実際にはコンポーネント システムとは言えません。せいぜい、DOM の単純なカプセル化として機能し、DOM をカスタマイズできるようになります。

このカルーセル コンポーネントを作成するには、最も単純な DOM 操作から始める必要があります。まず DOM 操作を使用してカルーセル機能全体を実装し、その後、段階的にコンポーネント システムに設計する方法を検討します。

ヒント: コンポーネントの開発を開始すると、機能をどのように設計すべきかを考えすぎて、非常に複雑な方法で実装してしまうことがよくあります。実際、より良い方法はその逆を行うことです。つまり、最初に機能を実装し、次にこの機能を分析してコンポーネント アーキテクチャ システムを設計します。

カルーセルなので当然画像を使う必要があるので、ここではUnsplashからオープンソースの画像4枚を用意しました。もちろん、自分の画像に置き換えることもできます。まず、次の 4 つの写真をgallery変数に格納します。

ギャラリー = [
 'https://source.unsplash.com/Y8lCoTRgHPE/1142x640',
 'https://source.unsplash.com/v7daTKlZzaw/1142x640',
 'https://source.unsplash.com/DlkF4-dbCOU/1142x640',
 'https://source.unsplash.com/8SQ6xjkxkCo/1142x640',
];

私たちの目標は、これら 4 つの画像を回転させることです。


コンポーネントの最下層のカプセル化

まず、このコンポーネントの記述を開始できるように、以前に記述したコードをカプセル化する必要があります。

  • ルートディレクトリにframework.jsを作成します。
  • createElementElementWrapperTextWrapper framework.jsファイルに移動する
  • 次に、要素を作成するこの基本的な方法を導入できるように、 createElementメソッドをエクスポートする必要があります。
  • ElementWrapperTextWrapper createElement によって内部的に使用されるため、エクスポートする必要はありません。
  • Wrapperクラスの共通部分をカプセル化する
  • ElementWrapperTextWrapperには同じsetAttributeappendChildmountToがあり、これらは繰り返され、共通です。
  • そこで、 Componentクラスを作成し、これら3つのメソッドをカプセル化します。
  • 次に、 ElementWrapperTextWrapperComponentを継承させます。
  • コンポーネントにrender()メソッドを追加
  •     コンポーネントクラスにコンストラクタを追加する

このようにして、コンポーネントの基盤となるフレームワークのコードをカプセル化しました。コード例は次のとおりです。

関数createElement(type, 属性, ...children) {
 // 要素を作成する let element;
 if (typeof type === 'string') {
 要素 = 新しい ElementWrapper(type);
 } それ以外 {
 要素 = 新しいタイプ();
 }

 // 属性をハングする for (let name in attribute) {
 要素.setAttribute(名前、属性[名前]);
 }
 // すべての子要素をハングする for (let child of children) {
 if (typeof child === 'string') child = new TextWrapper(child);
 要素.appendChild(子);
 }
 //最終的に要素はノードなので、直接返すことができます return element;
}

エクスポートクラスComponent {
 コンストラクタ() {
 }
 // 要素の属性をマウントする setAttribute(name, attribute) {
 this.root.setAttribute(名前、属性);
 }
 // 要素の子要素をマウントする appendChild(child) {
 子をマウントします(this.root);
 }
 // 現在の要素をマウントする mountTo(parent) {
 親.appendChild(this.root);
 }
}

クラスElementWrapperはComponentを拡張します{
 // コンストラクタ // DOMノードを作成するconstructor(type) {
 要素を作成します。
 }
}

TextWrapperクラスはComponentを拡張します{
 // コンストラクタ // DOMノードを作成するconstructor(content) {
 ルートディレクトリに document.createTextNode(content);
 }
}

カルーセルの実装

次に、 main.js変換を続けます。まず、 Div を Carousel に変更し、作成した Component 親クラスを継承させて、いくつかのメソッドの重複を省略する必要があります。

コンポーネントを継承した後、 framework.jsからコンポーネントをインポートする必要があります。

ここで正式にコンポーネントの開発を開始できますが、毎回 webpack を使用して手動でパッケージ化する必要がある場合は非常に面倒です。したがって、コードのデバッグを容易にするために、ここで webpack 開発サーバーをインストールしてこの問題を解決しましょう。

webpack-dev-serverをインストールするには、次のコードを実行します。

npm をインストール --save-dev webpack-dev-server webpack-cli 

上記の結果を見ると、インストールが成功したことがわかります。 webpack サーバーの実行フォルダーを構成することをお勧めします。ここでは、パッケージ化されたdistを実行ディレクトリとして使用します。

これを設定するには、 webpack.config.jsを開いてdevServerパラメータを追加し、 contentBaseパスを./distに指定する必要があります。

モジュール.エクスポート = {
 エントリ: './main.js',
 モード: '開発'、
 開発サーバー: {
 コンテンツベース: './dist',
 },
 モジュール: {
 ルール:
 {
 テスト: /\.js$/,
 使用: {
 ローダー: 'babel-loader',
 オプション:
 プリセット: ['@babel/preset-env'],
 プラグイン: [['@babel/plugin-transform-react-jsx', { プラグマ: 'createElement' }]],
 },
 },
 },
 ]、
 },
};

Vue や React を使用したことがある人なら、ローカル デバッグ環境サーバーを起動するには npm コマンドを実行するだけでよいことをご存知でしょう。ここでクイックスタートコマンドも設定します。 package.jsonを開き、 scripts構成に"start": "webpack start"行を追加します。

{
 "名前": "jsx-コンポーネント",
 "バージョン": "1.0.0",
 "説明": ""、
 "メイン": "index.js",
 「スクリプト」: {
 "test": "echo \"エラー: テストが指定されていません\" && exit 1",
 「開始」: 「webpack サーブ」
 },
 "著者": ""、
 「ライセンス」: 「ISC」、
 「devDependencies」: {
 "@babel/core": "^7.12.3",
 "@babel/plugin-transform-react-jsx": "^7.12.5",
 "@babel/プリセット環境": "^7.12.1",
 "バベルローダー": "^8.1.0",
 "webpack": "^5.4.0",
 "webpack-cli": "^4.2.0",
 "webpack-dev-server": "^3.11.0"
 },
 「依存関係」: {}
}

この方法では、次のコマンドを直接実行して、ローカル デバッグ サーバーを起動できます。

npm スタート

これをオンにすると、ファイルを変更するとそれが監視され、ファイルがリアルタイムでパッケージ化されるので、デバッグに非常に便利です。上の図に示すように、リアルタイム ローカル サーバーのアドレスはhttp://localhost:8080です。このアドレスをブラウザで直接開くと、このプロジェクトにアクセスできます。

ここで注意すべき点は、実行ディレクトリを dist に変更したことです。以前の main.html はルート ディレクトリに配置されていたため、localhost:8080 でこの HTML ファイルを見つけることができません。そのため、main.html を dist ディレクトリに移動し、main.js のインポート パスを変更する必要があります。

<!-- main.html コード -->
<本文></本文>

<script src="./main.js"></script>

リンクを開くと、Carousel コンポーネントが正常にマウントされていることがわかりました。これは、コードのカプセル化に問題がないことを証明しています。

次に、カルーセル機能の実装を続けます。まず、画像データをカルーセル コンポーネントに渡す必要があります。

a = <Carousel src={gallery}/> とします。

このようにしてgallery配列がsrc属性に設定されます。しかし、 src属性は Carousel 要素自体には使用されません。つまり、以前のようにthis.rootに直接マウントされるわけではありません。

したがって、この src にデータを個別に保存し、後でそれを使用してカルーセルの画像表示要素を生成する必要があります。 React では、要素のプロパティを格納するためにpropsが使用されますが、ここではattributesを使用して格納します。

受信した属性をthis.attributes変数に保存する必要があるため、Component クラスのconstructorでこのクラス属性を初期化する必要があります。

次に、これらの属性を要素ノードにマウントするのではなく、クラス プロパティに保存する必要があります。したがって、コンポーネント クラスでsetAttributeメソッドを再定義する必要があります。

コンポーネントがレンダリングされる前に src 属性の値を取得する必要があるため、レンダリング トリガーをmountTo内に配置する必要があります。

CarouselクラスはComponentを拡張します{
 // コンストラクタ // DOMノードを作成するconstructor() {
 素晴らしい();
 this.attributes = Object.create(null);
 }
 setAttribute(名前、値) {
 this.attributes[名前] = 値;
 }
 与える() {
	コンソールにログを記録します。
 document.createElement('div') を返します。
 }
 マウントする() {
 親要素に子要素を追加します。
 }
}

次に、実際に実行した結果を見て、画像データが取得できるかどうかを確認してみましょう。

次にこれらの写真を表示します。ここで、レンダリング メソッドを変更し、画像をレンダリングするロジックを追加する必要があります。

  • まず、新しく作成した要素を保存する必要があります
  • 画像データをループし、各データごとにimg要素を作成します。
  • 各img要素にsrc = 画像URLを添付します
  • src属性を持つ画像要素をコンポーネント要素this.rootにマウントします。
  • 最後に、レンダリングメソッドがthis.rootを返すようにします。
CarouselクラスはComponentを拡張します{
 // コンストラクタ // DOMノードを作成するconstructor() {
 素晴らしい();
 this.attributes = Object.create(null);
 }
 setAttribute(名前、値) {
 this.attributes[名前] = 値;
 }
 与える() {
 this.root = document.createElement('div');

 for (this.attributes.src の画像) {
 子要素を document.createElement('img') にします。
 child.src = 画像;
 this.root.appendChild(子);
 }

 this.root を返します。
 }
 マウント先(親) {
 親要素に子要素を追加します。
 }
} 

このようにして、画像がページ上に正しく表示されていることを確認できます。


タイポグラフィとアニメーション

まず、画像の要素はすべて img タグですが、このタグを使用すると、クリックしてドラッグするときにドラッグできます。もちろん、これは解決できますが、この問題をより簡単に解決するために、img を div に置き換えてから background-image を使用します。

デフォルトでは、div には幅も高さもないので、コンポーネントの div レイヤーにcarouselというクラスを追加し、HTML に CSS スタイルシートを追加して、carousel の下の各 div を直接選択し、適切なスタイルを適用する必要があります。

// メイン.js
CarouselクラスはComponentを拡張します{
 // コンストラクタ // DOMノードを作成するconstructor() {
 素晴らしい();
 this.attributes = Object.create(null);
 }
 setAttribute(名前、値) {
 this.attributes[名前] = 値;
 }
 与える() {
 this.root = document.createElement('div');
	this.root.addClassList('carousel'); // カルーセルクラスを追加

 for (this.attributes.src の画像) {
 子要素を document.createElement('div') にします。
 child.backgroundImage = `url('${picture}')`;
 this.root.appendChild(子);
 }

 this.root を返します。
 }
 マウント先(親) {
 親要素に子要素を追加します。
 }
}
<!-- main.html -->
<ヘッド>
 <スタイル>
 .carousel > div {
 幅: 500ピクセル;
 高さ: 281px;
 背景サイズ: 含む;
 }
 </スタイル>
</head>

<本文></本文>

<script src="./main.js"></script>

ここでは幅は 500 ピクセルですが、高さを 300 ピクセルに設定すると、画像の下部に画像が繰り返されることがわかります。これは、画像の比率が1600 x 900であり、 500 x 300比率が画像の元の比率と一致していないためです。

したがって、比例計算により、高さは次のようになります: 500 ÷ 1900 × 900 = 281.xxx 500\div1900\times900 = 281.xxx 500÷1900×900=281.xxx。したがって、500px の幅に対応する高さは約 281px になります。この方法では、画像を div 内で正常に表示できます。

カルーセルですべての画像を表示するのは明らかに不可能です。私たちが知っているカルーセルは、画像を 1 枚ずつ表示します。まず、画像の外側にあるカルーセル div 要素に、画像と同じ幅と高さのボックスを作成し、次にoverflow: hiddenを設定する必要があります。こうすることで、他の画像はボックスの外に広がり、隠れてしまいます。

生徒の中には、「他の画像を display: hidden または opacity: 0 に変更してはいかがですか?」と尋ねる人もいるかもしれません。カルーセルが回転しているとき、実際に現在の画像と次の画像を見ることができるからです。そのため、display: hidden などの hidden 属性を使用すると、以下の効果を実現することが難しくなります。

すると、別の問題が出てきます。カルーセル画像は一般的に左右にスライドし、上下にスライドすることはほとんどありません。しかし、ここではデフォルトで画像が上から下に配置されています。したがって、ここでは写真が一列に並ぶようにレイアウトを調整する必要があります。

ここでは通常のフローを使用できるため、div にdisplay: inline-blockを追加するだけで、一列に並べることができます。ただし、この属性だけでは、画像がウィンドウの幅を超えると自動的に折り返されるため、折り返しを強制しないようにするには、親にwhite-space: nowrap属性も追加する必要があります。以上です。

<ヘッド>
 <スタイル>
 .カルーセル{
 幅: 500ピクセル;
 高さ: 281px;
 空白: ラップなし;
 オーバーフロー: 非表示;
 }

 .carousel > div {
 幅: 500ピクセル;
 高さ: 281px;
 背景サイズ: 含む;
 表示: インラインブロック;
 }
 </スタイル>
</head>

<本文></本文>

<script src="./main.js"></script>

次に、自動カルーセル効果を実装しましょう。その前に、これらの画像要素にいくつかのアニメーション プロパティを追加しましょう。ここでは、要素アニメーションの継続時間を制御するためにtransitionを使用します。一般的に、1 フレームを再生するには0.5秒のeaseを使用します。

トランジションでは通常、ease プロパティのみが使用されます。ただし、ease-in が終了アニメーションに使用され、ease-out が開始アニメーションに使用される非常に特殊なケースを除きます。同じ画面では、通常はデフォルトでイージーを使用しますが、ほとんどの場合、リニアは使用しません。なぜなら、ease は人間の感覚に最も適した動作曲線だからです。

<ヘッド>
 <スタイル>
 .カルーセル{
 幅: 500ピクセル;
 高さ: 281px;
 空白: ラップなし;
 オーバーフロー: 非表示;
 }

 .carousel > div {
 幅: 500ピクセル;
 高さ: 281px;
 背景サイズ: 含む;
 表示: インラインブロック;
 遷移: 0.5 秒の緩和;
 }
 </スタイル>
</head>

<本文></本文>

<script src="./main.js"></script>

自動カルーセルを実装する

アニメーション効果プロパティを使用すると、JavaScript にタイマーを追加して、3 秒ごとに画像を切り替えることができます。この問題はsetInerval()関数を使用することで解決できます。

しかし、写真を回転させたり動かしたりするにはどうすればいいのでしょうか? HTML での移動について考えるとき、CSS のどの属性を使用して要素を移動できるか考えたことがありますか?

そうです、CSS で要素を移動するために特に使用されるtransformを使用します。したがって、ここでのロジックは、次の画像の先頭に移動できるように、要素を 3 秒ごとにその長さだけ左に移動することです。

しかし、これで移動できるのは 1 つの画像だけなので、2 回目に移動して 3 番目の画像に移動する必要がある場合は、各画像を 200% オフセットするなどする必要があります。したがって、現在のページ番号を表す値currentが必要になり、デフォルト値は 0 になります。移動するたびに 1 を加算するので、オフセット値は - 100 × ページ数 - 100\times ページ数 - 100 × ページ数になります。このようにして、写真の複数の動きが完成し、1枚ずつ表示されました。

CarouselクラスはComponentを拡張します{
 // コンストラクタ // DOMノードを作成するconstructor() {
 素晴らしい();
 this.attributes = Object.create(null);
 }
 setAttribute(名前、値) {
 this.attributes[名前] = 値;
 }
 与える() {
 this.root = document.createElement('div');
 this.root.classList.add('カルーセル');

 for (this.attributes.src の画像) {
 子要素を document.createElement('div') にします。
 child.style.backgroundImage = `url('${picture}')`;
 this.root.appendChild(子);
 }

 電流を 0 にします。
 間隔を設定する(() => {
 children = this.root.children; とします。
 ++現在;
 for (let child of children) {
 child.style.transform = `translateX(-${100 * current}%)`;
 }
 }, 3000);

 this.root を返します。
 }
 マウント先(親) {
 親要素に子要素を追加します。
 }
}

ここで問題が見つかりました。カルーセルが停止せず、左に移動し続けます。最後の画像にカルーセルする必要がある場合は、前の写真に戻る必要があります。

この問題を解決するには、数学的なトリックを使うことができます。数値を 1 から N の間で循環させたい場合は、それを n を法として計算するだけです。私たちの要素では、children の長さは 4 なので、current が 4 に達すると、4 ÷ 4 4\div4 4÷4 の余りは 0 になります。そのため、current を、current の余りを children の長さで割った値に設定するたびに、無限ループを実現できます。

ここで電流は 4 を超えることはなく、4 に達した後は 0 に戻ります。

このロジックを使用してカルーセルを実装すると、確かに画像が無限にループする可能性がありますが、実行して確認すると、別の問題が見つかります。最後の画像を再生した後、すぐに最初の画像にスライドすると、巻き戻し効果が表示されます。これは確かにあまり良くありません。私たちが望んでいるのは、最後の画像に到達した後、最初の画像が直接後ろに接続されることです。

それでは、一緒にこの問題を解いてみましょう。観察してみると、画面上には一度に 2 つの画像しか表示されません。したがって、実際には、これら 2 つの画像を正しい位置に移動するだけで済みます。

したがって、現在見ている画像と次の画像を見つけ、次の画像に移動するたびに次の画像を見つけて、次の画像を正しい位置に移動する必要があります。

この時点ではまだ少し混乱しているかもしれませんが、それは問題ではありません。論理を整理しましょう。

現在の画像のインデックスと次の画像のインデックスを取得します

  • まず、カルーセルは最初の画像から開始する必要があり、この画像はノードの0番目である必要があります。
  • 1枚目の写真を見たら2枚目の写真を準備する必要があるので、次の写真の位置を見つける必要がある。
  • 上で述べたように、数学的手法を使用して次の画像の位置を取得できます。次の画像の位置 = (現在の位置 + 1) ÷ 画像の数次の画像の位置 = (現在の位置 + 1)\div 画像の数次の画像の位置 = (現在の位置 + 1) ÷ 残りの画像の数。この式によると、最後の画像に到達すると、0 に戻り、最初の画像の位置に戻ります。

画像が移動する距離を計算し、現在の画像の後ろに移動を待つ画像を保持します。

  • 現在表示されている画像の位置は間違いなく正しいので、計算する必要はありません。
  • しかし、次の画像の位置は移動する必要があるので、ここではこの画像をオフセットする必要がある距離を計算する必要があります。
  • 各画像が1グリッド移動する距離は、その画像自体の長さに左への移動の負の数を加えた値に等しいため、左に移動する各グリッドは-100%になります。
  • 画像のインデックスは 0 から n までです。現在の画像から離れた画像の数としてインデックスを使用する場合、 index * -100%を使用して各画像を現在の画像の位置に移動できます。
  • しかし、まず必要なのは、現在の画像の次の位置に画像を移動することです。次の位置は、画像の距離のインデックス-1です。つまり、移動する必要がある距離は、 (index - 1) * -100%
  • 2 番目の画像を配置するためにアニメーション効果は必要ないので、このプロセスでは画像のアニメーション効果を無効にする必要があります。これを行うには、トランジションをクリアする必要があります。

2枚目の写真を配置し、カルーセル効果を開始できます

  • 少なくとも 1 フレームの画像移動時間が必要なので、カルーセル効果を実行する前に 16 ミリ秒の遅延が必要です (16 ミリ秒はブラウザの 1 フレームの時間と同じであるため)
  • まず、インラインタグのtransitionを再度オンにして、CSSのアニメーションが再び機能するようにします。次のカルーセル効果にはアニメーション効果が必要です。
  • 最初のステップは、現在の画像を 1 つ右に移動することです。 前に、インデックス位置にある任意の画像を現在の位置に移動するための式は index * -100% であると述べました。 さらに 1 つ右に移動したい場合は、 (index + 1) * -100%使用できます。
  • 2 番目のステップは、次の画像を現在の表示位置に移動することです。これは、index * -100% を直接使用して実行されます。
  • 最後に、レコードを 1 回更新して、 currentIndex = nextIndexとすれば完了です。

次に、上記のロジックを JavaScript に変換します。

CarouselクラスはComponentを拡張します{
 // コンストラクタ // DOMノードを作成するconstructor() {
 素晴らしい();
 this.attributes = Object.create(null);
 }
 setAttribute(名前、値) {
 this.attributes[名前] = 値;
 }
 与える() {
 this.root = document.createElement('div');
 this.root.classList.add('カルーセル');

 for (this.attributes.src の画像) {
 子要素を document.createElement('div') にします。
 child.style.backgroundImage = `url('${picture}')`;
 this.root.appendChild(子);
 }

 // 現在の画像のインデックス
 現在のインデックスを 0 にします。
 間隔を設定する(() => {
 children = this.root.children; とします。
 // 次の画像のインデックス
 nextIndex = (currentIndex + 1) % children.length; とします。

 // 現在の画像のノード let current = children[currentIndex];
 // 次の画像のノード let next = children[nextIndex]; 
	
 // 画像アニメーションを無効にする next.style.transition = 'none'; 
 // 次の画像を正しい位置に移動します next.style.transform = `translateX(${-100 * (nextIndex - 1)}%)`;
	
 // カルーセル効果を実行し、1フレームを16ミリ秒遅延します setTimeout(() => {
 // CSS でアニメーションを有効にする next.style.transition = ''; 
 // まず現在の画像を現在の位置から移動します current.style.transform = `translateX(${-100 * (currentIndex + 1)}%)`;
 // 次の画像を現在表示されている位置に移動します next.style.transform = `translateX(${-100 * nextIndex}%)`;
		
 // 最後に現在の位置のインデックスを更新します
 現在のインデックス = 次のインデックス;
 }, 16);
 }, 3000);

 this.root を返します。
 }
 マウント先(親) {
 親要素に子要素を追加します。
 }
}

最初にoverflow: hiddenを削除すると、すべての画像の移動軌跡が明確に表示されます。


ドラッグアンドドロップカルーセルを実装する

一般的に言えば、自動カルーセル機能に加えて、カルーセル コンポーネントをマウスでドラッグして回転させることもできます。それでは、この手動カルーセル機能を一緒に実装してみましょう。

自動カルーセルと手動カルーセルの間には一定の競合があるため、先ほど実装した自動カルーセル コードをコメント アウトする必要があります。次に、このカルーセル コンポーネントの下の子 (子要素)、つまりすべての画像の要素を使用して、手動ドラッグ カルーセル機能を実装できます。

ドラッグ機能は主に画像のドラッグを伴うため、画像にマウス リスニング イベントを追加する必要があります。操作手順の観点から考えると、次のような一連のロジックを思いつくことができます。

まずマウスを画像の上に移動してから、画像をクリックする必要があります。したがって、監視する必要がある最初のイベントはmousedownマウス プレス イベントである必要があります。マウスをクリックした後、マウスを動かし始め、マウスの動きの方向に画像が追従します。このとき、 mousemoveマウス移動イベントをリッスンする必要があります。画像を目的の位置までドラッグしたら、マウス ボタンを放します。このとき、画像を回転できるかどうかも計算する必要があります。そのためには、 mouseupマウス リリース イベントをリッスンする必要があります。

this.root.addEventListener('mousedown', イベント => {
 console.log('マウスダウン');
});

this.root.addEventListener('mousemove', イベント => {
 console.log('マウス移動');
});

this.root.addEventListener('mouseup', イベント => {
 console.log('マウスアップ');
});

上記のコードを実行すると、マウスを画像の上に置いて移動すると、 mousemoveが継続的にトリガーされることがコンソールに表示されます。しかし、私たちが望む効果は、マウスを押したまま動かしたときにのみmousemoveがトリガーされることです。画像上でマウスを動かすだけではイベントはトリガーされません。

したがって、マウスが押されたときに移動とリリースのアクションを正しく監視できるように、mousemove イベントと mouseup イベントを mousedown イベントのコールバック関数に配置する必要があります。また、マウスを離すと、mousemove と mouseup の 2 つのリスニング イベントを停止する必要があるため、それらを別々に保存する関数を使用する必要があることも考慮する必要があります。

this.root.addEventListener('mousedown', イベント => {
 console.log('マウスダウン');

 移動 = イベント => {
 console.log('マウス移動');
 };

 let up = イベント => {
 this.root.removeEventListener('mousemove', 移動);
 this.root.removeEventListener('mouseup', up);
 };

 this.root.addEventListener('mousemove', 移動);
 this.root.addEventListener('mouseup', up);
});

ここでは、マウスアップ時に mousemove イベントと mouseup イベントを削除します。これは、ドラッグするときに通常使用する基本コードです。

しかし、別の問題が見つかります。マウスをクリックしてドラッグし、放した後、画像上でマウスを再度動かすと、mousemove イベントが引き続きトリガーされます。

これは、マウスの動きがroot上で監視されるためです。実際、 mousedown はrootですでに監視されているため、 rootで mousemove と mouseup を監視する必要はありません。

したがって、これら 2 つのイベントをdocument上で直接リッスンできます。最新のブラウザでは、 documentリスニングを使用するとさらに利点があります。マウスがブラウザ ウィンドウの外に移動しても、イベントをリッスンできます。

this.root.addEventListener('mousedown', イベント => {
 console.log('マウスダウン');

 移動 = イベント => {
 console.log('マウス移動');
 };

 let up = イベント => {
 document.removeEventListener('mousemove', 移動);
 document.removeEventListener('mouseup', up);
 };

 document.addEventListener('mousemove', 移動);
 document.addEventListener('mouseup', 上);
});

この完全な監視メカニズムを使用して、mousemove でカルーセルの移動機能を実装することができます。この関数のロジックを整理してみましょう。

これを行うには、まずマウスの位置を知る必要があります。ここでは、mousemove のeventパラメータを使用して、マウスの座標を取得します。実際には、 eventにはoffsetXoffsetYなど、多くのマウス座標があります。これらは、異なる参照システムに基づいて取得された座標です。ここでは、 clientXclientYこれらの座標は、ブラウザ全体のレンダリング可能な領域を基準としており、いかなる要因にも影響されません。多くの場合、コンポーネントはブラウザのコンテナー内にあり、ページをスクロールすると、一部の座標系が変わります。この方法では簡単に解決不可能なバグが発生する可能性がありますが、clientX と clientY ではこの問題は発生しません。画像を特定の方向にどれだけ移動させるかを知りたい場合は、マウスをクリックしたときの開始座標を把握し、取得した clientX および clientY と比較する必要があります。したがって、 startXstartYを記録する必要があります。それらのデフォルト値は、対応する現在の clientX と clientY です。したがって、マウスが移動する距離は、終点座標 - 開始点座標 終点座標 - 開始点座標 終点座標 - 開始点座標 となり、移動コールバック関数ではclientX - startXclientY - startYカルーセルは左右のスライドのみをサポートしているため、このシナリオでは Y 軸の値は必要ありません。次に、移動距離を計算した後、対応するドラッグされた要素に変換を追加して、画像を移動できるようにします。以前、自動カルーセルを作成したときに、画像要素に遷移アニメーションを追加しました。ドラッグ時にこのアニメーションがあると、遅延効果が発生します。そのため、画像に変換を追加するときは、その遷移属性も無効にする必要があります。

this.root.addEventListener('mousedown', イベント => {
 children = this.root.children; とします。
 startX = event.clientX; とします。

 移動 = イベント => {
 x = event.clientX - startX とします。
 for (let child of children) {
 child.style.transition = 'なし';
 child.style.transform = `translateX(${x}px)`;
 }
 };

 let up = イベント => {
 document.removeEventListener('mousemove', 移動);
 document.removeEventListener('mouseup', up);
 };

 document.addEventListener('mousemove', 移動);
 document.addEventListener('mouseup', 上);
});

さて、ここで 2 つの問題が見つかりました。

最初にクリックしてドラッグすると、画像の開始位置は正しいのですが、もう一度クリックすると、画像の位置が間違っています。画像をドラッグした後、マウスボタンを放すと、画像はドラッグを終了した位置に留まります。ただし、通常のカルーセルコンポーネントでは、画像を特定の位置を超えてドラッグすると、自動的に次の画像に回転します。

これら 2 つの問題を解決するには、次のように計算できます。これは、作成しているのがカルーセル コンポーネントであるためです。現在の一般的なカルーセル コンポーネントによると、画像を画像の半分より大きい位置にドラッグすると、次の画像に回転します。位置が半分未満の場合、現在ドラッグしている画像の位置に戻ります。

このような要件に従って、現在の画像(0 から始まる)を記録するpositionを記録する必要があります。各画像の幅が 500 ピクセルの場合、最初の画像の現在の値は 0、オフセット距離は 0 * 500 = 0、2 番目の画像は 1 * 500 ピクセル、3 番目の画像は 2 * 500 ピクセル、というようになります。この規則によれば、N番目の画像のオフセット位置はn ∗ 500 n * 500 n∗500となります。

まず、マウスを動かすときに、現在の画像が開始点からどれだけ移動したかを計算する必要があります。これは N * 500 で計算できます。ここで、N は現在の画像のposition値です。次に、マウスを離したときに現在の画像が移動した距離が画像の半分の長さを超えているかどうかも計算する必要があります。超過している場合は、次の画像の開始点に直接変換します。ここでの超過の判断は、現在のマウスが移動した距離xを各画像の長度で割ることによって使用できます (コンポーネントは画像を 500 ピクセルに制御するため、x を 500 で割ります)。これにより、0 から 1 の間の数値が得られます。この数値が 0.5 以上の場合、画像の長さが半分に達したことを意味し、次の画像に直接回転できます。0.5 未満の場合は、現在の画像の開始位置に戻ることができます。上記で計算された値は、 positionと組み合わせることもできます。0.5 以上の場合は 1 に丸められ、それ以外の場合は 0 になります。ここでの 1 は、 positionを 1 に追加できることを意味します。0 の場合は、 position変更されません。これにより、現在の値が直接変更され、変換時に新しい現在の値に応じて自動的に計算され、カルーセル効果が実現されます。 x左右に移動できる距離なので、マウスを左に動かすとx負の数になり、その逆も同様です。マウスを左にドラッグすると前に移動し、右にドラッグすると後に移動します。したがって、ここでこの超出值を計算する場合、 position = position - Math.round(x/500) 。たとえば、マウスを左に 400 ピクセル移動し、現在の値が 0 の場合、 position = 0 - Math.round(400/500) = 0 - -1 = 0 + 1 = 1となり、最終的に現在の値は1なります。上記のロジックによれば、カルーセル内のすべての子画像を mouseup イベントでループし、それらに新しい変換値を設定する必要があります。

this.root.addEventListener('mousedown', イベント => {
 children = this.root.children; とします。
 startX = event.clientX; とします。

 移動 = イベント => {
 x = event.clientX - startX とします。
 for (let child of children) {
 child.style.transition = 'なし';
 child.style.transform = `translateX(${x - current * 500}px)`;
 }
 };

 let up = イベント => {
 x = event.clientX - startX とします。
 現在の値 = 現在の値 - Math.round(x / 500);
 for (let child of children) {
 child.style.transition = '';
 child.style.transform = `translateX(${-current * 500}px)`;
 }
 document.removeEventListener('mousemove', 移動);
 document.removeEventListener('mouseup', up);
 };

 document.addEventListener('mousemove', 移動);
 document.addEventListener('mouseup', 上);
});

ここで、画像の長さとして500使用していることに注意してください。これは、独自に作成した画像コンポーネントの画像の幅が 500 ピクセルに固定されているためです。一般的なカルーセル コンポーネントを作成する必要がある場合は、要素の実際の幅を取得するのが最適ですElement.clientWith() 。このようにして、コンポーネントはユーザーに合わせて変更できます。

これを実行すると、ドラッグを使用して写真を回転できますが、最後の写真までドラッグすると、最後の写真の後に空白があり、最初の写真が最後の写真に接続されていないことがわかります。

その後、この機能を改善していきます。これは、実際には自動カルーセルと非常によく似ています。自動カルーセルを作成していたとき、写真をカルーセルするたびに最大 2 枚の写真しか表示されないことがわかっていました。カルーセルの幅がページに対して非常に狭いため、3 枚の写真が表示される可能性は非常に低かったです。この問題は、ユーザーが 2 枚目の写真をドラッグするのに十分なスペースがない限り発生しません。しかし、ここではこの要素については考慮しません。

ドラッグするたびに 2 つの画像しか表示されないことが確実なので、ドラッグ カルーセルも自動カルーセルのように処理できます。しかし、ここでは違いが 1 つあります。画像を自動的に回転すると、画像は左または右のいずれかの方向にのみ移動します。ただし、手動で左または右にドラッグすると、画像を任意の方向に移動できます。したがって、この機能を実装するために自動カルーセル コードを直接使用することはできません。カルーセルの最初と最後の無限ループのロジックを自分で再処理する必要があります。

position + Math.round(x/500) current : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :前一個元素: :下一個元素[-1, 0, 1]當前元素: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : -1圖片位置= 當前圖片位置+ 偏移: : : : : : : : : : : : : : : : : : : : : : : : : -1 : : :したがって、計算された位置が負の数になった場合は、それをこの画像列の最後の画像の位置に変換する必要があります。この例の画像データによれば、現在の画像は位置 0 にあるため、前の画像は位置 3 にある画像になります。では、最後に -1 を 3 に、4 を 0 にするにはどうすればよいでしょうか?ここで、ちょっとした数学のトリックを使う必要があります。先頭と末尾の値が互いに超過したときに反転するようにしたい場合は、 (當前指針+ 數組總長度)/ 數組總長度余數求める式を使用する必要があります。得られた余りは、正確に反転されます。

この式が正しいことを証明しましょう。まず、current = 0 に遭遇した場合、位置 0 にある前の画像にはポインター -1 が設定されます。このとき、(− 1 + 4) / 4 = 3 / 4 (-1 + 4) / 4 = 3 / 4 (−1+4)/4=3/4 を使用します。ここで、3 を 4 で割った余りは 3 であり、 3この配列の最後の画像です。

それでは試してみましょう。現在の画像が配列の最後の画像である場合、この例では 3 なので、3 + 1 = 4 です。このとき、(4 + 4) / 4 (4 + 4) / 4 (4+4)/4 に変換すると、余りは0なります。明らかに、取得した数字は配列の最初の画像の位置です。

この式を通じて、この時点で前の画像と次の画像のポインターを取得して、ノードでオブジェクトを取得し、CSSDOMを使用して、すべての要素を最初に変更する必要があります。

ロジック全体を整理しました。MouseMoveイベントコールバック関数コードをどのように記述するかを見てみましょう。

move = event => {
 x = event.clientx -startx;

 current = position -math.round(x/500);

 for([-1、0、1]のオフセットとしましょう){
 pos = current + offsetとします。
 //画像が配置されているインデックスを計算します
 pos =(pos + children.length)%children.length;
 console.log( 'pos'、pos);

 子供[pos] .style.transition = 'none';
 子供[pos] .style.transform = `translatex($ { -  pos * 500 + offset * 500 +(x%500)} px)`;
 }
};

非常に多くのことについて話した後、コードはほんの数行です。したがって、コードを書くプログラマーも計り知れない可能性があります。

最後に、小さな問題があります。前の写真と次の写真に奇妙なジャンプがあることがわかります。

この問題はMath.round(x / 500)によって引き起こされます。変換すると、 x % 500を追加し、現在の値計算には計算のこの部分が含まれていないため、マウスがドラッグされるとこの部分のオフセットが欠落します。

整数を摂取するのと同じ効果を達成するには、ここでMath.round(x / 500)(x - x % 500) / 500に変更する必要があり、同時にxの元の正と負の値を保持できます。

実際には、MouseUpイベントでロジックを変更していません。次に、ロジックをどのように実装するかを見てみましょう。

ここで変更する必要があるのは、子供のループのコードです。ここでのロジックは、実際には基本的に移動と同じですが、ここに変更する必要がある場所がいくつかあります。

まず、遷移禁止は削除できます。これは、 ' 'がリリースされたときであり、マウスの位置を計算する必要があります。飛行要素は実際には必要ない要素であり、フライング+ x % 500は以前に使用した[ pos = current + offset ]の-1と1の2つの要素に由来するため、UPロジックでは不必要なものを削除する必要があります。これは、マウスが左に移動した場合、-1の要素のみが必要であることを意味します。それどころか、1の要素のみが必要であり、他の要素を削除できることを意味します。まず第一に、 for ofループの順序要件はないため、2つの数値-1と1を式を置き換えて、0の後に配置できます。しかし、どのように必要な側面を見つけることができますか?実際、計算する必要があるのは、画像の方向です。そのため、変更する必要があるのは、Code position = position - Math.round(x / 500) Math.round(x / 500) - x介して取得できます。この値は、現在の要素- Math.sign(Math.round(x / 500) - x)ネガティブ)または右に傾いています。実際、別の小さなバグがあります。現在の画像が短すぎると、画像位置の計算が正しくありません。

これは、Match.Round()の特性のためです。250(500pxは正確に半分)であるため、絵をラウンドにする必要がある方向を計算した後、 + 250 * Match.sign(x)を追加する必要があります。

最終的に、私たちのコードは次のようなものです:

LET UP = event => {
 x = event.clientx -startx;
 position = position -math.round(x/500);

 for([0、-math.sign(math.round(x / 500)-x + 250 * math.sign(x))のオフセットを){
 pos = position + offsetとします。
 //画像が配置されているインデックスを計算します
 pos =(pos + children.length)%children.length;

 子供[pos] .style.transition = '';
 子供[pos] .style.transform = `translatex($ { -  pos * 500 + offset * 500} px)`;
 }

 document.removeEventListener('mousemove', 移動);
 document.removeeventlistener( 'mouseup'、up);
};

UP機能を変更した後、このマニュアルカルーセルコンポーネントを本当に完成させました。


これは、JSXを使用してCarousel Carouselコンポーネントを実装するための詳細については、以下の記事を検索してください。

以下もご興味があるかもしれません:
  • Vue3 のレンダリング関数における互換性のない変更の詳細な説明
  • Vueレンダリング関数renderの使い方の詳しい説明
  • Vueレンダリング機能の詳しい説明
  • Vue で jsx 構文を正しく使用する方法
  • React-vscode で jsx 構文を使用する際の問題と解決策
  • JSX を使用してコンポーネント パーサー開発を構築する例
  • Vueコンポーネントjsx構文の具体的な使用
  • Vue jsx の使用ガイドと vue.js での jsx 構文の使用方法
  • Vue が JSX 構文をサポートする方法の詳細な説明
  • レンダリング関数と JSX の詳細

<<:  MySql バージョンの問題に対する完璧なソリューション sql_mode=only_full_group_by

>>:  Hadoop 2.Xの新機能、ごみ箱機能の説明

推薦する

Windows 環境での MySQL 8.0 のインストール、設定、アンインストール

ソフトウェアバージョンウィンドウズ: ウィンドウズ10 MySQL: mysql-8.0.17-wi...

HTMLの基礎 HTMLの構造

HTML ファイルとは何ですか? HTML は Hyper Text Markup Language...

MySQLデータベースの名前を高速かつ安全に変更する方法(3種類)

目次MySQLデータベースの名前を変更する方法最初の方法: データベースの名前を変更することは非推奨...

MySQL SELECT文の実行方法

MySQL Select ステートメントはどのように実行されますか?最近、Geek Time で D...

Ubuntu 20.04 に MySql5.7 をインストールして構成するための詳細なチュートリアル

目次1. Ubuntuソースの変更2. MySQLをインストールする3. 新しいユーザーを作成し、権...

VMware のインストールと使用時の問題と解決策

仮想マシンは使用中であるか、接続できません次のようなエラーが報告された場合解決まずこのページにアクセ...

Linux で scp コマンドを使用してファイルをリモートでコピーする方法の詳細な説明

序文scp は secure copy の略です。scp は、Linux システムの ssh ログイ...

MySQL が「operate_time」エラーのデフォルト値が無効であると報告する問題を解決する

データベースでcreate tableステートメントを実行する テーブル `sys_acl` を作成...

HTML ウェブページハイパーリンクタグ

HTML ウェブ ページのハイパーリンク タグの学習チュートリアル リンク タグの属性 リンクは、ウ...

体験をデザインする: ボタンには何があるか

<br />最近、UCDChina は「インターフェース上のテキストに注意を払う」という...

CentOS8 システムをベースにした Gitlab を構築するために Docker を使用する詳細なチュートリアル

目次1. Dockerをインストールする2. GitLabをインストールする3. GitLabを初期...

LeetCode の SQL 実装 (196. 重複するメールボックスを削除する)

[LeetCode] 196.重複したメールを削除するSQL クエリを記述して、Person とい...

MySQLカバーインデックスの使用例

カバーインデックスとは何ですか?クエリで使用されるすべてのフィールドを含むインデックスを作成すること...

トランザクションとロックを表示するための MySQL の一般的なステートメント

データベース内のトランザクションとロックを表示するための一般的なステートメントトランザクションの待機...

ウェブページのコアコンテンツ(画像とテキスト)の視覚的表現の紹介

情報の最適化と改良は常にデザインの最初のステップです。 「これは百度アライアンスユーザーエクスペリエ...