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(); });