milvinae

web & product design.

変わったGalleryをCSSアニメーションで作る



前回はanime.jsを使ってギャラリーを作りました。
でも、よくよく考えてみればただ位置がうごいて少し回転するだけ…
これ、cssで作れないか?
遷移先の位置をランダムに作ってcssアニメーションで動かせばいいんじゃなかろうか。

実装してみましょう。

※Codepenで使用している画像は引き続きPhotockさんのフリー素材です。
https://photock.jp/

See the Pen corkboard test by keiyashi (@keiyashi) on CodePen.


htmlに変わりはありません。
jsを見ていきましょう。

const picNumber = 5;
const picMax    = 25;
const picIdPrefix = "pic";
var   gitpass = "https://raw.githubusercontent.com/keiyashi/codepen-example/refs/heads/main/pic-";
var   fileSuffix = ".webp";

function initialize(){
  var back = document.querySelector(".backboard");
  var counted = [];
  for(i = 0; i < picNumber; i++){
    var num = Math.ceil(Math.random()*picMax);
    while(counted.includes(num)){
      if(num == picMax) num = 1;
      else num++;
    }
    counted.push(num);
    var picture = document.createElement("figure",{id: picIdPrefix + i}); 
    picture.classList.add("picture-frame");
    var pictureInner = document.createElement("img");
    pictureInner.classList.add("picture-inner");
    pictureInner.src = gitpass + num + fileSuffix;
    picture.appendChild(pictureInner);
    back.appendChild(picture);
  }
}

function pictureLoad(){
  var pictureInners = document.querySelectorAll(".picture-inner");
  var counted = [];

  pictureInners.forEach(inner => {
    var num = Math.ceil(Math.random()*picMax);
    while(counted.includes(num)){
      if(num == picMax) num = 1;
      else num++;
    }
    counted.push(num);
    inner.src = gitpass + num + fileSuffix;
  });
}

document.addEventListener("DOMContentLoaded", (e) =>{
  initialize();
  addAnimation();
})

function addAnimation(){
  var board = document.querySelector(".backboard");
  var boardRect = board.getBoundingClientRect();
  var frames = document.querySelectorAll(".picture-frame");
  frames.forEach(frame => {
    var frameRect = frame.getBoundingClientRect();
    frame.classList.add("frame-anime");
    frame.style.setProperty("--duration", Math.floor(Math.random() * 2000) / 1000  + "s");
    frame.style.setProperty("--transx", Math.floor(Math.random() * (boardRect.width  - frameRect.width)) + "px");
    frame.style.setProperty("--transy", Math.floor(Math.random() * (boardRect.height - frameRect.height)) + "px");
    frame.style.setProperty("--rotate", Math.floor(Math.random() * 90 - 45) + "deg");
  })
}

function clickPin(){
  removePic();
  pictureLoad();
  addAnimation();
}

function removePic(){
  var frames = document.querySelectorAll(".picture-frame");
  frames.forEach(frame => {
    frame.classList.remove("frame-anime");
  })
}

アニメーションを担当するクラスを作って、画像がロードされたらクラスを付与する、というのが方針です。
なのでjsの重要な部分は大きく2つです。
1.画像要素の生成
2.アニメーションの付与

生成に関しては前回とほぼ同じで、initializeで生成しています。
アニメーションクラスの付与はaddAnimationで行っています。
詳しい話はcssのほうでお話ししますが、イメージ的には遷移先、かかる時間、回転角を1枚1枚ランダムで計算して渡してる感じです。

ではcssです。
.backboard{
  width: 90dvw;
  height: 90dvh;
  position:relative;
  left:5dvw;
  top:5dvh;
  background-image:  url("https://raw.githubusercontent.com/keiyashi/codepen-example/refs/heads/main/CorkBoard.jpg");
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  padding: 15px;
  box-shadow: 2dvw 2dvw 2dvw rgba(64,64,64,.3);
  border-radius:20px;  
    
  .pin{
    position:absolute;
    width: 50px;
    aspect-ratio:1;
    top: -5dvh;
    right: -1dvw;
    background-color: transparent;
    border-color:transparent;
    cursor: pointer;
  }
}

.picture-frame{
  position:absolute;
  width:15dvw;
  aspect-ratio:1.5;
  background-color:white;
  transform-origin:center;
  box-shadow:0px 0px 3px rgba(64, 64, 64, .8);
  
  img{
    position:relative;
    width:92%;
    height:92%;
    top:4%;
    left:4%;
    color:lightblue;
  }
}

.frame-anime{
  animation-name: scatterPic;
  animation-duration:var(--duration);
  animation-iteration-count:1;
  animation-fill-mode:both;
  --transx:0px;
  --transy:0px;
  --rotate:0deg;
  --duration:0s;
}

@keyframes scatterPic{
  0%{
    transform:translate(0px, 0px) rotate(0deg);
    opacity:0;
  }
  100%{
    transform:translate(var(--transx), var(--transy)) rotate(var(--rotate));
    opacity:1;
  }
}


@keyframesを使ってアニメーションを指示します。
画像がロード(リロード)されたときに、frame-animeクラスを画像たちに付与することでアニメーションが作動します。
animation-fill-modeは、アニメーションの開始と終了時の状態を維持するかどうか、を指定するもの。
ばらまいた先で止まってほしいのでforwardsまたはbothを指定します。
https://developer.mozilla.org/ja/docs/Web/CSS/animation-fill-mode
今回はアニメーション0%の時の状態から始まってほしいのでbothにしました。

クラスで指定したスタイルは、同じクラスをもつすべての要素に共通の値が適用してしまいます。
ですが、変数を作ってjavascriptで要素1つ1つの変数を変えることでバラバラの値を指定することが出来ます。
今回は遷移先X,Y、回転角、アニメーションの動作時間の4つを変数にして、javascriptでframe-animation付与時に乱数を与えています。


画像を更新するときは、frame-animeクラスを剥奪 → 画像の中身を更新 → 再度frame-animeクラスを付与するという流れです。
前回と同じようにばらまいた位置から更にばらまきなおすようにするには、ばらまいた位置を変数で記録するようにすれば実現できます。



これでanime.jsを使わずにばらまきギャラリーを作ることが出来ました。
ライブラリ汚染、プラグイン汚染を防ぐためにも可能な限り自前で作ってしまうほうがいいですね。