
いい感じのホームページを見ると、その表現を自分でも表現したくなるクセのある、ユゲタです。
WebサイトのHTMLやCSSのみで構築されているとある程度コピペできてしまいますが、Javascriptで処理されていると、もはや追うのはカオスです。
むしろ、自分でプログラミングした方がよっぽど早いでしょう。
という事で、いい感じの複数画像の切替を行ういいデザインを見つけたので、それを自分コードで書いてみたいと思います。
完成デモ
ソースコード
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="main.js"></script>
</head>
<body>
  <h1>Image Shift</h1>
  <div class="image-wrap">
    <img src="img/001.jpg"/>
    <img src="img/002.jpg"/>
    <img src="img/003.jpg"/>
  </div>
</body>
</html>
style.css
.image-wrap{
  overflow : hidden;
  width    : 300px;
  height   : 300px;
  position : relative;
}
.image-wrap > img{
  position   : absolute;
  width      : calc(100% + var(--move));
  height     : 100%;
  top        : 0;
  left       : 0;
  object-fit : cover;
}
.image-wrap > img[data-wipe]{
  animation : img-wipe var(--wipe) ease-in-out 0s forwards;
}
@keyframes img-wipe{
  0%{
    clip-path : polygon(0 0, 100% 0, 100% 100%, 0 100%);
  }
  100%{
    clip-path : polygon(100% 0, 100% 0, 100% 100%, 100% 100%);
  }
} 
main.js
function Main(){
  this.slide = 5000
  this.move  = 50
  this.wipe  = 1000
  this.shift = this.slide - this.wipe * 2
  this.init()
  this.sort_reverse()
  this.set_slides()
  setTimeout(this.set_wipe.bind(this) , this.shift)
}
// 初期設定(css連動用のプロパティ変数定義)
Main.prototype.init = function(){
  const roots = document.querySelectorAll(`.image-wrap`)
  for(const root of roots){
    root.style.setProperty("--slide", `${this.slide}ms`, "")
    root.style.setProperty("--move" , `${this.move}px`, "")
    root.style.setProperty("--wipe" , `${this.wipe}ms`, "")
  }
}
// DOMの順番を逆にする(DOM最終が先頭で表示されるため)
Main.prototype.sort_reverse = function(){
  const roots = document.querySelectorAll(`.image-wrap`)
  for(const root of roots){
    const imgs = root.querySelectorAll(`:scope > img`)
    for(const img of imgs){
      root.insertBefore(img, root.firstElementChild)
    }
  }
}
// 表示されている画像(last-child)の移動開始処理
Main.prototype.set_slides = function(){
  const roots = document.querySelectorAll(`.image-wrap`)
  for(const root of roots){
    const img = root.querySelector(`:scope > img:last-child`)
    this.set_slide_animate(img)
  }
}
// 画像のゆっくり移動開始
Main.prototype.set_slide_animate = function(img){
  const move = -this.move
  img.animate([
    {"transform"  : `translateX(${move}px)`},
    {"transform"  : `translateX(0)`},
  ],{
    duration: this.slide
  })
}
// ワイプ開始
Main.prototype.set_wipe = function(){
  const roots = document.querySelectorAll(`.image-wrap`)
  for(const root of roots){
    const img = root.querySelector(`:scope > img:last-child`)
    img.setAttribute("data-wipe" , true)
    const img2 = root.querySelector(`:scope > img:nth-last-child(2)`)
    this.set_slide_animate(img2)
  }
  setTimeout(this.sort_change.bind(this) , this.wipe)
}
// 最終を先頭にもってくる
Main.prototype.sort_change = function(){
  const roots = document.querySelectorAll(`.image-wrap`)
  for(const root of roots){
    const img = root.querySelector(`:scope > img:last-child`)
    root.insertBefore(img, root.firstElementChild)
    img.removeAttribute("data-wipe")
    img.removeAttribute("data-slide")
  }
  setTimeout(this.set_wipe.bind(this) , this.shift)
}
// ページのDOM読み込みが完了した後でMain処理を起動
switch(document.readyState){
  case "complete":
  case "interactive":
    new Main()
  break
  default:
    window.addEventListener("DOMContentLoaded" , (() => new Main()))
  break
}
※画像は適当なものをご用意ください。
解説
複数の画像を、.image-wrapというselector要素の中に入れておくと、自動的にどんどん無限に切り替わって表示してくれるという仕様です。
そして、表示順番は、HTMLに書かれたIMGタグの順番通りに切り替わるのもポイントです。
でも、HTMLのDOM構造では、一番最後に書かれたタグ(imgタグ)が、一番手前に表示されるので、cssのtransform3d()を使って、表示順番の入れ替えを行っても言いのですが、
今回は、DOMの順番をreverse(逆転)させて、一番最後に設置されたimgタグが表示される仕組みにしています。
そして、切り替え時に、一番下のDOMを一番上に移動させることで、一定処理で無限に画像が切り替わる処理を実現しています。
2つのアニメーション
この画像切替処理は、2つのアニメーションを組み合わせて行っています。
1つ目は、画像がちょっとずつ右にスライドする、移動処理。
2つ目は、画像切替のワイプ処理。
スライド処理は、Javascriptで行って、ワイプ処理はcssでセットしています。
スライド処理
Javascript処理と言っても、中身はCSSです。
そのままの画像を移動させるのは、transformのtranslate()を使って簡単に移動させることはできますが、画像サイズによって、移動距離などが上手く行かない場合があるので、
どんな画像でも、どのアスペクト比(縦横サイズ)でも対応できるように、画像サイズを移動させる分だけ、大きくして、はみ出さにようにtransformで移動させています。
ワイプ処理
ワイプ処理は、css3のclip-path機能を使って、さらにそれをアニメーションさせることで、切り替わりっぽく見せています。
この処理はかなり効率的で、他にもいろいろなワイプ機能が作れるメリットがあります。
clip-pathがわからない人は、以前ブログに書いた記事があるので、そちらを読んでもらえるとわかりやすいと思います。
以前のブログはこちら→ 
[CSS] もうアイコン画像は不要。HTMLタグとCSSだけでシンプルアイコンが設置できる方法
画像について
デモでは3枚の画像ファイルを設置していますが、imgタグは、いくつセットしてもそれに合わせて順番に無限に切り替わるようになります。
これは、実際にホームページを運用する時など、画像を増やしたり、入れ替えたりしたくなるケースも多いと思い、コードに枚数指定や、順番設定などは入れないように工夫しました。
単に横スライドさせるのでは芸が無いと思っていたんですが、スライド+ワイプで切り替わる画像って、結構Webサイトの間が持つ感じが非常にいいですね。
あとがき
チョット見ただけだと、CSSで簡単にできてしまうと思った方は、是非ご自身で構築してみてください。
これが落とし穴が多くて、以外に大変で、デモの作成だけで2時間超えぐらい構築に掛かってしまいました。
つまづきポイントはいくつかあり、複数のアニメーションをタイミングを少しだけズラしながらスタートさせるという処理は、javascriptで同期をとりながらやるのが一番やりやすかったので、今回はJavascriptバージョンにすることにしました。
スライド処理も最初はcssのみで行っていたんですが、何故か最初の移動アニメだけが動かないという、意味不明な事象が確認されたので、急いでJS版に作り変えたという裏話もあります。
できるだけ効率よく短く簡単なコードで書くように心がけたんですが、コードだけを見ると難しく感じる人も多いかもしれません。
コピペでも動くと思うので、このコード使いたい人は是非内容理解も含めて色々と自分なりに変更して、内容理解してみてください。
不明点があれば、お便りで聞いて下さいね。
 
0 件のコメント:
コメントを投稿