2018秋の無料相談会
2018秋の無料相談会
2018.07.14
#154
それいけ!フロントエンド

ベジェ曲線を使ってCSSでアニメーションさせてみた

はっちゃん

どうも、フロントエンドエンジニアのはっちゃんです。
暑い日が続いて、もう本格的に夏ですね。夏季休暇はどこいこうかなー。

……と、言っても、なんも予定ないからとりあえず卓球します。

さて今回のブログは、「ベジェ曲線をつかってCSSでアニメーションさせてみた」ということなんですが、案件で「弧線を描いてキャラクターを動かす」という要件があったので、その工程をまとめた内容になります。

今回のゴール


オブジェクト(サンプルは球体)が目標に向かって繰り返し放物線を描きながら落ちるアニメーションを実装します。

考え方

オブジェクト一つ一つをDOMで用意し、それぞれをanimationプロパティでアニメーションさせます。その際の放物線の動きを計算する関数をSass(SCSS)で作り、引数に渡して繰り返し実行します。

値を受け取って出力する側の関数をSass(SCSS)で書いてみる

参考にした記事はこちらです。
http://endlab.net/web/bezier.html
記事内の下記JS関数を参考にしてSass(SCSS)に置き換えました。

function getPointQB(t,x1,y1,x2,y2,x3,y3) {
  var tp = 1 - t,
      x = t*t*x3 + 2*t*tp*x2 + tp*tp*x1,
      y = t*t*y3 + 2*t*tp*y2 + tp*tp*y1;

  return [x,y];
}

ちょっと公式の説明は難しいので、割愛させていただきます……笑。
また、Sass(SCSS)だと値を複数返せないので、式を分解して、X軸用とY軸用の2つに関数を分けました。

 
X軸

@function getPointX($t,$x1,$x2,$x3) {
	$tp: 1 - $t;
	$x: $t*$t*$x3 + 2*$t*$tp*$x2 + $tp*$tp*$x1;

	@return $x + px;
}

 
Y軸

@function getPointY($t,$y1,$y2,$y3) {
	$tp: 1 - $t;
	$y: $t*$t*$y3 + 2*$t*$tp*$y2 + $tp*$tp*$y1;

	@return $y + px;
}

 
値をtranslateに置き換える
mixinの中から先ほどの関数を実行します。

@mixin object-keyframes($startX, $startY, $ctrlX, $ctrlY, $endX, $endY) {
	@for $i from 0 through 100 {
		#{$i}% {
			transform: translate(getPointX($i * 0.01, $startX, $ctrlX, $endX), getPointY($i * 0.01, $startY, $ctrlY, $endY));
		}
	}
}

 

値を渡し、出力結果を受け取るSass(SCSS)を書いてみる

起点をpositionのtopとleftで設定し、translateで動かします。

.object__circle__item {
	position: absolute;
	border-radius: 50%;
	&.object__circle__item--1 {
		top: -30px;
		left: -30px;
		width: 20px;
		height: 20px;
		background-color: #FCB374;
		transform: translate(0px, 0px);
		animation: object-1 6s linear 0s infinite;
	}
	&.object__circle__item--2 {
		top: -30px;
		left: 0;
		width: 15px;
		height: 15px;
		background-color: #FFF4D8;
		transform: translate(0px, 0px);
		animation: object-2 5s linear 4s infinite;
	}

        ・
        ・
        ・
}
@keyframes object-1 {
	$startX: 0; // 開始位置X
	$startY: 0; // 開始位置Y
	$ctrlX: 220; // 制御点X
	$ctrlY: 100; // 制御点Y
	$endX: 240; // 終了位置X
	$endY: 330; // 終了位置Y
	@include object-keyframes($startX, $startY, $ctrlX, $ctrlY, $endX, $endY);
}

@keyframes object-2 {
	$startX: 0; // 開始位置X
	$startY: 0; // 開始位置Y
	$ctrlX: 200; // 制御点X
	$ctrlY: 130; // 制御点Y
	$endX: 220; // 終了位置X
	$endY: 330; // 終了位置Y
	@include object-keyframes($startX, $startY, $ctrlX, $ctrlY, $endX, $endY);
}

・
・
・

 

コンパイルされたCSSを見てみる

コンパイルすると、下記のようなkeyframeが出力されます。

@keyframes object-1 {
  0% {
    transform: translate(0px, 0px); }
  1% {
    transform: translate(4.38px, 2.013px); }
  2% {
    transform: translate(8.72px, 4.052px); }
  3% {
    transform: translate(13.02px, 6.117px); }
  4% {
    transform: translate(17.28px, 8.208px); }
  5% {
    transform: translate(21.5px, 10.325px); }
  6% {
    transform: translate(25.68px, 12.468px); }
  7% {
    transform: translate(29.82px, 14.637px); }
  8% {
    transform: translate(33.92px, 16.832px); }
  9% {
    transform: translate(37.98px, 19.053px); }
  10% {
    transform: translate(42px, 21.3px); }
  11% {
    transform: translate(45.98px, 23.573px); }
  12% {
    transform: translate(49.92px, 25.872px); }
  13% {
    transform: translate(53.82px, 28.197px); }
  14% {
    transform: translate(57.68px, 30.548px); }
  15% {
    transform: translate(61.5px, 32.925px); }
  16% {
    transform: translate(65.28px, 35.328px); }
  17% {
    transform: translate(69.02px, 37.757px); }
  18% {
    transform: translate(72.72px, 40.212px); }
  19% {
    transform: translate(76.38px, 42.693px); }
  20% {
    transform: translate(80px, 45.2px); }
  21% {
    transform: translate(83.58px, 47.733px); }
  22% {
    transform: translate(87.12px, 50.292px); }
  23% {
    transform: translate(90.62px, 52.877px); }
  24% {
    transform: translate(94.08px, 55.488px); }
  25% {
    transform: translate(97.5px, 58.125px); }
  26% {
    transform: translate(100.88px, 60.788px); }
  27% {
    transform: translate(104.22px, 63.477px); }
  28% {
    transform: translate(107.52px, 66.192px); }
  29% {
    transform: translate(110.78px, 68.933px); }
  30% {
    transform: translate(114px, 71.7px); }
  31% {
    transform: translate(117.18px, 74.493px); }
  32% {
    transform: translate(120.32px, 77.312px); }
  33% {
    transform: translate(123.42px, 80.157px); }
  34% {
    transform: translate(126.48px, 83.028px); }
  35% {
    transform: translate(129.5px, 85.925px); }
  36% {
    transform: translate(132.48px, 88.848px); }
  37% {
    transform: translate(135.42px, 91.797px); }
  38% {
    transform: translate(138.32px, 94.772px); }
  39% {
    transform: translate(141.18px, 97.773px); }
  40% {
    transform: translate(144px, 100.8px); }
  41% {
    transform: translate(146.78px, 103.853px); }
  42% {
    transform: translate(149.52px, 106.932px); }
  43% {
    transform: translate(152.22px, 110.037px); }
  44% {
    transform: translate(154.88px, 113.168px); }
  45% {
    transform: translate(157.5px, 116.325px); }
  46% {
    transform: translate(160.08px, 119.508px); }
  47% {
    transform: translate(162.62px, 122.717px); }
  48% {
    transform: translate(165.12px, 125.952px); }
  49% {
    transform: translate(167.58px, 129.213px); }
  50% {
    transform: translate(170px, 132.5px); }
  51% {
    transform: translate(172.38px, 135.813px); }
  52% {
    transform: translate(174.72px, 139.152px); }
  53% {
    transform: translate(177.02px, 142.517px); }
  54% {
    transform: translate(179.28px, 145.908px); }
  55% {
    transform: translate(181.5px, 149.325px); }
  56% {
    transform: translate(183.68px, 152.768px); }
  57% {
    transform: translate(185.82px, 156.237px); }
  58% {
    transform: translate(187.92px, 159.732px); }
  59% {
    transform: translate(189.98px, 163.253px); }
  60% {
    transform: translate(192px, 166.8px); }
  61% {
    transform: translate(193.98px, 170.373px); }
  62% {
    transform: translate(195.92px, 173.972px); }
  63% {
    transform: translate(197.82px, 177.597px); }
  64% {
    transform: translate(199.68px, 181.248px); }
  65% {
    transform: translate(201.5px, 184.925px); }
  66% {
    transform: translate(203.28px, 188.628px); }
  67% {
    transform: translate(205.02px, 192.357px); }
  68% {
    transform: translate(206.72px, 196.112px); }
  69% {
    transform: translate(208.38px, 199.893px); }
  70% {
    transform: translate(210px, 203.7px); }
  71% {
    transform: translate(211.58px, 207.533px); }
  72% {
    transform: translate(213.12px, 211.392px); }
  73% {
    transform: translate(214.62px, 215.277px); }
  74% {
    transform: translate(216.08px, 219.188px); }
  75% {
    transform: translate(217.5px, 223.125px); }
  76% {
    transform: translate(218.88px, 227.088px); }
  77% {
    transform: translate(220.22px, 231.077px); }
  78% {
    transform: translate(221.52px, 235.092px); }
  79% {
    transform: translate(222.78px, 239.133px); }
  80% {
    transform: translate(224px, 243.2px); }
  81% {
    transform: translate(225.18px, 247.293px); }
  82% {
    transform: translate(226.32px, 251.412px); }
  83% {
    transform: translate(227.42px, 255.557px); }
  84% {
    transform: translate(228.48px, 259.728px); }
  85% {
    transform: translate(229.5px, 263.925px); }
  86% {
    transform: translate(230.48px, 268.148px); }
  87% {
    transform: translate(231.42px, 272.397px); }
  88% {
    transform: translate(232.32px, 276.672px); }
  89% {
    transform: translate(233.18px, 280.973px); }
  90% {
    transform: translate(234px, 285.3px); }
  91% {
    transform: translate(234.78px, 289.653px); }
  92% {
    transform: translate(235.52px, 294.032px); }
  93% {
    transform: translate(236.22px, 298.437px); }
  94% {
    transform: translate(236.88px, 302.868px); }
  95% {
    transform: translate(237.5px, 307.325px); }
  96% {
    transform: translate(238.08px, 311.808px); }
  97% {
    transform: translate(238.62px, 316.317px); }
  98% {
    transform: translate(239.12px, 320.852px); }
  99% {
    transform: translate(239.58px, 325.413px); }
  100% { 
  	transform: translate(240px, 330px);
  }
}

 

実際に表示してみる

GitHubに公開したのでこちらをご覧ください。
https://82mou.github.io/scss-bezier-curve-animation/dist/

CSS animationだとkeyframeの%毎のイージングになってしまうので、全体のイージングを設定できればなおよかったのですが……、JSを使わなくてもこれくらいはイケちゃいますね!

全ソースはGithubをご参照ください。
https://github.com/82mou/scss-bezier-curve-animation/tree/gh-pages

 

まとめ

いかがでしたか? 工夫すれば、3次ベジェ曲線で波状に動かすこともできます。ぜひ試してみてくださいね!