jQuery(function($) { // WordPress推奨のjQuery記述方法 // --- 設定項目 ---
const voiceDataUrl = 'https://www.denkishuri.com/wp/wp-content/uploads/voice-data.json'; // ★ 574件のJSONファイルのURL
const loadMoreCount = 20; //「もっと見る」で追加する件数 // --- DOM要素の取得 ---
const $cardsContainer = $('#voice-cards-container-target');
const $filterButtons = $('.filter-btn');
// --- 状態管理 ---
let allVoices = [];
let displayedVoices = [];
let loadedCount = 0;
let observer; let currentFilters = {
job: 'all',
age: 'all'
}; // --- 関数定義 --- // 【変更なし】この関数は元のままです
const createCardHTML = (voice) => {
const jobDisplayNames = { "manufacturing": "製造・生産現場", "building": "ビル設備管理・メンテナンス", "construction": "電気工事・施工管理", "design": "設計・開発・技術職", "other": "その他" };
const ageDisplayNames = { "10s": "10代", "20s": "20代", "30s": "30代", "40s": "40代", "50s": "50代", "60s": "60代~" };
const jobDisplayName = jobDisplayNames[voice.job] || "その他";
const ageDisplayName = ageDisplayNames[voice.age] || voice.age;
// ★変更点:改行コード(\n)をHTMLの改行タグ(
)に変換します
const textWithBreaks = voice.text.replace(/\n/g, '
'); return `${voice.heading}
${textWithBreaks}
${ageDisplayName} ${jobDisplayName}
`;
}; // 【追加】カードに「もっと見る」機能を追加する関数
const addToggleFunctionality = ($card) => {
const $text = $card.find('.voice-card-text');
const textElement = $text.get(0); // 本文の実際の高さが、CSSで設定したmax-heightを超えているかチェック
// toFixed(0)で小数点以下を丸めて比較の精度を上げる
if (textElement.scrollHeight.toFixed(0) > $text.height().toFixed(0)) {
$text.addClass('is-long'); // 末尾をぼかすグラデーション用のクラス const $button = $('');
$button.insertAfter($text); // 本文の直後にボタンを挿入 // ボタンクリック時の動作
$button.on('click', function() {
const $btn = $(this);
$text.toggleClass('is-open'); if ($text.hasClass('is-open')) {
$btn.text('閉じる');
$text.removeClass('is-long'); // 開いたらグラデーションを消す
} else {
$btn.text('もっと見る');
$text.addClass('is-long'); // 閉じたらグラデーションを再度表示
}
});
}
};
// 【変更】カードを追加描画する関数を修正
const renderMoreVoices = () => {
const voicesToRender = displayedVoices.slice(loadedCount, loadedCount + loadMoreCount);
if (voicesToRender.length === 0) {
if (observer) observer.disconnect();
return;
} voicesToRender.forEach(voice => {
const cardHTML = createCardHTML(voice);
const $newCard = $(cardHTML); // HTML文字列をjQueryオブジェクトに変換
$cardsContainer.append($newCard);
addToggleFunctionality($newCard); // ★追加:生成したカードに「もっと見る」機能を適用
}); loadedCount += voicesToRender.length;
if (loadedCount >= displayedVoices.length) {
if (observer) observer.disconnect();
}
}; // 【変更なし】この関数は元のままです
const applyFiltersAndRenderInitial = () => {
if (observer) observer.disconnect(); displayedVoices = allVoices.filter(voice => {
const jobMatch = (currentFilters.job === 'all') || (voice.job === currentFilters.job);
const ageMatch = (currentFilters.age === 'all') || (voice.age === currentFilters.age);
return jobMatch && ageMatch;
}); $cardsContainer.empty();
loadedCount = 0;
renderMoreVoices(); if (loadedCount < displayedVoices.length) {
setupIntersectionObserver();
}
};
// 【変更なし】この関数は元のままです
const setupIntersectionObserver = () => {
const options = {
root: null,
rootMargin: '0px',
threshold: 0.1
}; observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
obs.unobserve(entry.target);
renderMoreVoices();
const lastCard = $cardsContainer.children().last().get(0);
if (lastCard) {
obs.observe(lastCard);
}
}
});
}, options); const lastCard = $cardsContainer.children().last().get(0);
if (lastCard) {
observer.observe(lastCard);
}
}; // 【変更なし】この関数は元のままです
$filterButtons.on('click', function() {
const $button = $(this);
const filterGroup = $button.data('filter-group');
const filterValue = $button.data('filter');
currentFilters[filterGroup] = filterValue; $filterButtons.filter(`[data-filter-group="${filterGroup}"]`).removeClass('active');
$button.addClass('active');
applyFiltersAndRenderInitial();
}); // 【変更なし】この関数は元のままです
const init = async () => {
if ($cardsContainer.length === 0) {
console.error('致命的エラー: ターゲットコンテナが見つかりません。');
return;
} try {
const response = await fetch(voiceDataUrl);
if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
allVoices = await response.json(); // 「成果の声(success_story)」を並び替える処理は、JSONにtypeがないため一旦コメントアウト
// allVoices.sort((a, b) => (a.type === 'success_story' ? -1 : 1) - (b.type === 'success_story' ? -1 : 1)); applyFiltersAndRenderInitial();
} catch (error) {
console.error('お客様の声の読み込み、または表示処理に失敗しました:', error);
$cardsContainer.html(`お客様の声の読み込みに失敗しました。
`);
}
}; init();
});