人気投票
目次
JSでカウントし、クリックアニメーションを実装
期間限定コンテンツ案件を制作する際に、発見があったので、メモ。人気投票のように、クリックするとカウントされる仕組みを実装することに。サイトを訪れる人にどれが人気かが可視化されるイメージです。難しくつくる必要もなかったので、jsとjsonとphpで制作しました。ただし、クリックするだけだと面白味に欠けるかなと思い、エフェクトっぽいことができないか調べ、なんとなくいい感じにできたのが今回の収穫です。
カウントアップJS
対象が10個あり、それぞれにライクボタンとカウント用の数字があります。ライクボタンの形はハートにしました。HTMLは以下のような形で、これが10箇所ある感じです。
<dl class="heartArea">
<dt class="heart">
<img src="/images/heart.svg" alt="いいね!" class="heart__img">
</dt>
<dd class="val">0</dd>
</dl>
続いて、js。fetchData()でjsonからカウントを読み込み、各ボタンをクリックする処理はforEachを用いています。このままだと、カウンターの数字が表示されないので、画面読み込時にshowNumber()を実行し、表示させることにしました。また、一度クリックしたらクリックを機能させないようにしたかったので、addEventListnerのオプションにonce: trueとしています。加えて、aタグでラップされたものの中にライクボタンが存在し、クリックできるようにしていたため、hrefのリンク先に遷移させないために、stopPropagation()とpreventDefault()で伝播しないようにしました。
countはハートをコピーする数で、for文で指定の数だけクリックされたハートをコピーし、その数分だけハートが乱れ飛びます。classにtargetを付け加え、後述のanime.jsやCSSで制御しやすくしました。
const btns = document.querySelectorAll('.heartArea .heart__img');
const countText = document.querySelectorAll('.heartArea .val');
const count = 30;
async function fetchData() {
const response = await fetch('/counts.json', { cache: "reload" });
const json = await response.json();
return json;
}
btns.forEach(function(btn, index) {
btn.addEventListener('click', async function (e) {
e.stopPropagation();
e.preventDefault();
for (let i = 0; i < count; i++) {
let hearts = btn.cloneNode(false)
hearts.classList.add('target')
btn.after(hearts)
}
//読み込み
const countNum = await fetchData();
let newCountNum = countNum;
newCountNum[index] = countNum[index] + 1;
//書き込み
let fd = new FormData();
fd.append('json', JSON.stringify(newCountNum));
fetch('jsonSave.php', {
method: 'post',
body: fd
})
.then(response => response.text())
.then(data => {
// console.log(data);
});
//表示
this.parentNode.nextElementSibling.innerText = newCountNum[index];
}, { once: true });
});
async function showNumber() {
let countNum = await fetchData();
for(let i=0; i < btns.length; i++) {
btns[i].parentNode.nextElementSibling.innerText = countNum[i];
}
}
window.addEventListener("DOMContentLoaded", showNumber);
アニメーションは使い慣れているanime.jsを使用。動きの実装は下記サイトを参考にしました。
btns.forEach(function(btn, index) {
const tl = anime.timeline({});
btn.addEventListener('click', async function (e) {
e.stopPropagation();
e.preventDefault();
for (let i = 0; i < count; i++) {
let hearts = btn.cloneNode(false)
hearts.classList.add('target')
btn.after(hearts)
}
const targets = document.querySelectorAll('.target')
anime({
targets: targets,
easing: 'easeOutCubic',
scale: function () {
return anime.random(3, 0.5)
},
translateX: function () {
return anime.random(45, -45)
},
translateY: function () {
return anime.random(25, -40)
},
opacity: [1, 0],
duration: 800,
delay: anime.stagger(10),
complete: () => {
targets.forEach((target) => {
target.remove()
})
}
})
tl.add({
targets: btn,
scale: [1, 5],
opacity: [1, 0],
duration: 800,
easing: 'easeOutExpo',
}).add({
targets: btn,
scale: [0 ,1],
opacity: 0.3,
duration: 400,
});
//読み込み
const countNum = await fetchData();
let newCountNum = countNum;
newCountNum[index] = countNum[index] + 1;
//書き込み
let fd = new FormData();
fd.append('json', JSON.stringify(newCountNum));
fetch('jsonSave.php', {
method: 'post',
body: fd
})
.then(response => response.text())
.then(data => {
// console.log(data);
});
//表示
this.parentNode.nextElementSibling.innerText = newCountNum[index];
}, { once: true });
});
json
jsonファイルはカウントの保存です。単純な作りで、0~9までの配列を事前に準備しておきます。後述のphpにより、該当する配列の数字をカウントアップして上書きしています。
{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0}
jsonに保存するphp
jsonに保存するphpも単純で、jsonファイルを開いて、送信されたデータをファイルに保存し、ファイルを閉じるだけ。念のため、バックアップが欲しかったので、最後はバックアップ用の記述です。そうそう、jsonファイルの場所がパスからばれているので、外から利用されたくない場合は、パーミッションで保護してあげる必要があります。
<?php
$data = json_decode($_POST['json']);
var_dump($data);
$json = fopen('text.json', 'w+');
flock($json, LOCK_EX);
fwrite($json, json_encode($data));
flock($json, LOCK_UN);
fclose($json);
if($data) {
copy('text.json', 'bak_text.json');
}