どうも、フロントエンドエンジニアのはっちゃんです。
暑い日が続いて、もう本格的に夏ですね。夏季休暇はどこいこうかなー。
……と、言っても、なんも予定ないからとりあえず卓球します。
さて今回のブログは、「ベジェ曲線をつかってCSSでアニメーションさせてみた」ということなんですが、案件で「弧線を描いてキャラクターを動かす」という要件があったので、その工程をまとめた内容になります。
目次
今回のゴール
オブジェクト(サンプルは球体)が目標に向かって繰り返し放物線を描きながら落ちるアニメーションを実装します。
考え方
オブジェクト一つ一つをDOMで用意し、それぞれをanimationプロパティでアニメーションさせます。その際の放物線の動きを計算する関数をSass(SCSS)で作り、引数に渡して繰り返し実行します。
値を受け取って出力する側の関数をSass(SCSS)で書いてみる
記事内の下記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次ベジェ曲線で波状に動かすこともできます。ぜひ試してみてくださいね!
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。