クリップパスを動かす(HTML/CSS/JS)

前書き

クリップパス(clip-path)をアニメーションさせる方法を掲載しています。

目次

  1. 準備
  2. 三点
  3. 四点
  4. 組合せ
  5. 参考

準備

下記サイトを参考にしてパスを設定しています。

CSS clip-path maker

GSAP(GreenSock社が開発しているJavaScriptライブラリ)を使用しています。
こちらのページからダウンロード出来ます。

GSAP Installation

当ページでは、2つのファイルを読み込んでいます。


import { gsap } from './gsap_3.10.4/esm/gsap-core.js';
import { CSSPlugin } from './gsap_3.10.4/esm/CSSPlugin.js';

三点

要素を重ねて配置して、それぞれ、clip-pathを変化させています。
九つの点を準備して、そこから三つの点を選んでいます。
いろは
いろは
再開
にほへと
にほへと
にほへと
にほへと
再開
ちりぬるを
ちりぬるを
ちりぬるを
ちりぬるを
再開

<div class="wrap">
  <div class="item item01 js-311">いろは</div>
  <div class="item item02 js-312">いろは</div>
</div>
<div class="wrap">
  <div class="item item01 js-321">にほへと</div>
  <div class="item item02 js-322">にほへと</div>
  <div class="item item03 js-323">にほへと</div>
  <div class="item item04 js-324">にほへと</div>
</div>
<div class="wrap">
  <div class="item item01 js-331">ちりぬるを</div>
  <div class="item item02 js-332">ちりぬるを</div>
  <div class="item item03 js-333">ちりぬるを</div>
  <div class="item item04 js-334">ちりぬるを</div>
</div>

.wrap{
  position: relative; height: 5.5em;
}
.item{
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; justify-content: center; align-items: center;
  writing-mode: vertical-rl; text-orientation: upright;
}
.item01{ color: #595757; background: #c9c9ca; }
.item02{ color: #e5e6e6; background: #417abe; }
.item03{ color: #e5e6e6; background: #346c9c; }
.item04{ color: #e5e6e6; background: #35526d; }

// 座標
const div = [0, 50, 100];
const num = div.length;
const point = [];
let key = 1;
for (let i = 0; i < num; i++) {
  for (let j = 0; j < num; j++) {
    point[key] = div[j] + "% " + div[i] + "%"; //1: "0% 0%" ... 9: "100% 100%"
    key++
  }
}
//座標を代入
const cp3 = (a, b, c) =>  {
  return "polygon(" + point[a] + "," + point[b] + "," + point[c] + ")";
}
//座標を動かす1------------------
const tl31 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init31 = () => {
  gsap.set(".js-312", {clipPath: cp3(1, 4, 7)}); //左端
}
const motion31 = () => {
  tl31.to(".js-312", {clipPath: cp3(1, 5, 7)})
      .to(".js-312", {clipPath: cp3(2, 7, 6)})
      .to(".js-312", {clipPath: cp3(9, 8, 2)})
      .to(".js-312", {clipPath: cp3(7, 9, 2)})
}
init31();
motion31();

//座標を動かす2------------------
const tl32 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init32 = () => {
  gsap.set(".js-322", {clipPath: cp3(9, 9, 9)}); //右下角
  gsap.set(".js-323", {clipPath: cp3(1, 1, 1)}); //左上角
  gsap.set(".js-324", {clipPath: cp3(7, 7, 7)}); //左下角
}
const motion32 = () => {
  tl32.to(".js-322", {clipPath: cp3(1, 7, 9)})
      .to(".js-323", {clipPath: cp3(1, 9, 3)})
      .to(".js-324", {clipPath: cp3(3, 7, 9)});
}
init32();
motion32();

//座標を動かす3------------------
const tl33 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init33 = () => {
  gsap.set(".js-334", {clipPath: cp3(1, 1, 7)}); //左端
  gsap.set(".js-333", {clipPath: cp3(7, 3, 3)});
  gsap.set(".js-332", {clipPath: cp3(7, 6, 6)});
}
const motion33 = () => {
  tl33.to(".js-334", {clipPath: cp3(1, 3, 7)})
      .to(".js-333", {clipPath: cp3(7, 3, 6)})
      .to(".js-332", {clipPath: cp3(7, 6, 9)});
}
init33();
motion33();

四点

要素を重ねて配置して、それぞれ、clip-pathを変化させています。
九つの点を準備して、そこから四つの点を選んでいます。
わかよ
わかよ
再開
たれそ
たれそ
たれそ
たれそ
再開
つねならむ
つねならむ
つねならむ
再開

<div class="wrap">
  <div class="item item01 js-411">わかよ</div>
  <div class="item item02 js-412">わかよ</div>
</div>
<div class="wrap">
  <div class="item item01 js-421">たれそ</div>
  <div class="item item02 js-422">たれそ</div>
  <div class="item item03 js-423">たれそ</div>
  <div class="item item04 js-424">たれそ</div>
</div>
<div class="wrap">
  <div class="item item01 js-431">つねならむ</div>
  <div class="item item02 js-432">つねならむ</div>
  <div class="item item03 js-433">つねならむ</div>
</div>

//三点と同じ

// 座標
const div = [0, 50, 100];
const num = div.length;
const point = [];
let key = 1;
for (let i = 0; i < num; i++) {
  for (let j = 0; j < num; j++) {
    point[key] = div[j] + "% " + div[i] + "%"; //1: "0% 0%" ... 9: "100% 100%"
    key++;
  }
}
//座標を代入
const cp4 = (a, b, c, d) =>  {
  return "polygon(" + point[a] + "," + point[b] + "," + point[c] + "," + point[d] + ")";
}
//座標を動かす1------------------
const tl41 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init41 = () => {
  gsap.set(".js-412", {clipPath: cp4(1, 1, 4, 4)}); //左端上半分
}
const motion41 = () => {
  tl41.to(".js-412", {clipPath: cp4(1 ,2, 5, 4)}) //左上半分
      .to(".js-412", {clipPath: cp4(2, 7, 8, 3)}) //平行四辺形
      .to(".js-412", {clipPath: cp4(2, 6, 8, 4)}) //菱形
      .to(".js-412", {clipPath: cp4(9, 1, 3, 7)}) //ねじれ
}
init41();
motion41();

//座標を動かす2------------------
const tl42 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init42 = () => {
  gsap.set(".js-422", {clipPath: cp4(7, 7, 8, 8)});
  gsap.set(".js-423", {clipPath: cp4(8, 8, 9, 9)});
  gsap.set(".js-424", {clipPath: cp4(4, 4, 7, 7)});
}
const motion42 = () => {
  tl42.to(".js-422", {clipPath: cp4(7, 2, 3, 8)})
     .to(".js-423", {clipPath: cp4(1, 8, 9, 2)})
      .to(".js-424", {clipPath: cp4(4, 3, 6, 7)})
}
init42();
motion42();

//座標を動かす3------------------
const tl43 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init43 = () => {
  gsap.set(".js-432", {clipPath: cp4(1, 1, 4, 4)});
  gsap.set(".js-433", {clipPath: cp4(6, 6, 9, 9)});
}
const motion43 = () => {
  tl43.to(".js-432", {clipPath: cp4(1, 3, 6, 4)})
     .to(".js-433", {clipPath: cp4(6, 4, 7, 9)})
      .to(".js-432", {clipPath: cp4(1, 3, 3, 7), duration: 0.5})
      .to(".js-433", {clipPath: cp4(3, 7, 7, 9), duration: 0.5},"<")
      .set(".js-432", {clipPath: cp4(1, 3, 7, 7)})
     .set(".js-433", {clipPath: cp4(3, 3, 7, 9)})
      .to(".js-432", {clipPath: cp4(1, 2, 8, 7), duration: 0.5})
      .to(".js-433", {clipPath: cp4(3, 2, 8, 9), duration: 0.5},"<")
}
init43();
motion43();

要素を重ねて配置して、それぞれ、clip-pathを変化させています。
九つの点を準備して、1つの点を選んで、半径を指定しています。
うゐの
うゐの
再開
おくやま
おくやま
おくやま
おくやま
再開
けふこえて
けふこえて
けふこえて
けふこえて
再開

<div class="wrap">
  <div class="item item01 js-511">うゐの</div>
  <div class="item item02 js-512">うゐの</div>
</div>
<div class="wrap">
  <div class="item item01 js-521">おくやま</div>
  <div class="item item02 js-522">おくやま</div>
  <div class="item item03 js-523">おくやま</div>
  <div class="item item04 js-524">おくやま</div>
</div>
<div class="wrap">
  <div class="item item01 js-531">けふこえて</div>
  <div class="item item02 js-532">けふこえて</div>
  <div class="item item03 js-533">けふこえて</div>
  <div class="item item04 js-534">けふこえて</div>
</div>

//三点と同じ

// 座標
const div = [0, 50, 100];
const num = div.length;
const point = [];
let key = 1;
for (let i = 0; i < num; i++) {
  for (let j = 0; j < num; j++) {
    point[key] = div[j] + "% " + div[i] + "%"; //1: "0% 0%" ... 9: "100% 100%"
    key++
  }
}
//座標を代入
const cp2 = (a, b) =>  {
  return "circle(" + a + " at " + point[b] + ")";
}

//座標を動かす1------------------
const tl21 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init21 = () => {
  gsap.set(".js-212", {clipPath: cp2("0%", 5)});
}
const motion21 = () => {
  tl21.to(".js-212", {clipPath: cp2("50%", 5)})
      .to(".js-212", {clipPath: cp2("50%", 4)})
}
init21();
motion21();

//座標を動かす2------------------
const tl22 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init22 = () => {
  gsap.set(".js-222", {clipPath: cp2("0%", 7)});
  gsap.set(".js-223", {clipPath: cp2("0%", 9)});
  gsap.set(".js-224", {clipPath: cp2("0%", 5)});
}
const motion22 = () => {
  tl22.to(".js-222", {clipPath: cp2("100%", 7)})
      .to(".js-223", {clipPath: cp2("100%", 9)})
      .to(".js-224", {clipPath: cp2("50%", 5)})
}
init22();
motion22();

//座標を動かす3------------------
const tl23 = gsap.timeline({
  defaults: {ease: "power0", duration: 1}
});
const init23 = () => {
  gsap.set(".js-234", {clipPath: cp2("0%", 1)}); //左端
  gsap.set(".js-233", {clipPath: cp2("0%", 1)}); //左端
  gsap.set(".js-232", {clipPath: cp2("0%", 1)}); //左端
}
const motion23 = () => {
  tl23.to(".js-234", {clipPath: cp2("40%", 1)})
      .set(".js-233", {clipPath: cp2("40%", 1)})
      .to(".js-233", {clipPath: cp2("40%", 5)})
      .set(".js-232", {clipPath: cp2("40%", 5)})
      .to(".js-232", {clipPath: cp2("40%", 9)})
}
init23();
motion23();

組合せ

こちらの素材を利用して、イラストレーターからパスを取得して、パスを相対化しています。

イラスト素材:ポリゴンサークル-水色
早く知りたかった!フロアマップの座標を一括取得できる意外な方法
要素を重ねて配置して、それぞれ、clip-pathを変化させています。
切替

<div class="wrap50">
  <div class="item50 js-511"></div>
</div>

.wrap50{
  position: relative; padding: 100% 0 0;
}
.item50{
  position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #c9c9ca;
}

// 絶対値
const pathObtained = [
  [32,170, 29,176, 35,172, 32,170],
  //中略
  [567,335, 573,249, 578,163, 577,161, 493,264, 567,335],
];
//相対化
const width = 614;
const height = 613;
const pathRelative = (arr, w, h) => {
  for (let i = 0; i < arr.length; i++) {
    arr[i] = arr[i].map((value, index) => {
      if( value % 2 == 0){
        return Math.round( value / w * 100 * 100 ) / 100;
      }else{
        return Math.round( value / h * 100 * 100 ) / 100;
      }
    });
  }
  return arr;
}
const pathStandard = pathRelative(pathObtained, width, height);
//座標を乱す
const getRandom = () => {
  return Math.floor( Math.random() * ( 8 - 3 ) + 3 ) * [-1,1][Math.floor(Math.random() * 2)];
}
const pathRandom = (arr) => {
  for (let i = 0; i < arr.length; i++) {
    arr[i] = arr[i].map((value) => {
      if( value % 2 == 0){
        return value + getRandom();
      }else{
        return value + getRandom();
      }
    });
  }
  return arr;
}
//座標の代入
const cpx = (arr) =>  {
  let val = "polygon(";
  for (let i = 0; i < arr.length; i++) {
    if( i % 2 == 0 && i != 0){
      val += "," + arr[i] + "%";
    }else{
      val += arr[i] + "%";
    }
  }
  val += ")";
  return val;
}
//要素を複製
const num = pathStandard.length - 1;
const wrap = document.querySelector(".js-51");
const target = document.querySelector(".js-511");
for (let i = 0; i < num; i++){
  const targetClone = target.cloneNode(true);
  wrap.appendChild( targetClone );
}
//座標を動かす
const init51 = () => {
  gsap.set(".js-511", {
    clipPath: (index) => { return cpx(pathStandard[index]); },
    opacity : "random(0.4, 1, 0.01)",
  });
}
let count = 1;
const bgColorArr = ["#c9c9ca","#417abe","#35526d","#a675a5","#f18d1d"];
const motion51 = () => {
  let pathNext = [].concat(pathStandard); //pathStandardを複製
  if(count % 3 == 0){
    //基準の座標
  }else{
    pathNext = pathRandom(pathNext); //座標を乱す
  }
  gsap.to(".js-511", {
    clipPath: (index) => {
      return cpx(pathNext[index]);
    },
    opacity : "random(0.4, 1, 0.01)",
    backgroundColor: bgColorArr[count % 5],
    duration: 1,
  });
  count ++;
}
init51();
motion51();

参考

CSS clip-path maker

CSS clip-path の使い方

早く知りたかった!フロアマップの座標を一括取得できる意外な方法

関連記事

文字に簡単な動きをつける(HTML/CSS/JS)

GSAP ScrollTriggerを使う(HTML/JS)

CSSで文字に光彩のような効果を加える

マスク、クリップパス(CSS)

先頭に戻る
ページの先頭に戻る