📝

ClaudeとJavaScriptで作る男脳・女脳診断テスト - 実装解説

に公開

はじめに

書籍「話を聞かない男、地図が読めない女」に掲載されている診断テストを、JavaScriptとWordPressを使ってWebアプリケーション化しました。30問の質問に答えるだけで、思考パターンを分析できる診断システムです。

この記事で分かること

  • HTML/CSS/JavaScriptによる診断テストの実装方法
  • WordPressのAJAX機能を使った利用回数カウンター機能
  • レスポンシブ対応の診断UIの作り方
  • スコア計算ロジックとリアルタイム結果表示

この記事の対象者

  • JavaScript初心者から中級者の方
  • WordPressでインタラクティブなコンテンツを作りたい方
  • 診断系Webアプリに興味があるエンジニア

動作環境・使用技術

  • HTML5 / CSS3 / Vanilla JavaScript
  • WordPress 6.8.1
  • Lightning テーマ 15.29.11
  • レスポンシブデザイン対応

自己紹介

ホネグミ代表、応用情報技術者の資格を持つエンジニア×マーケターです。これまでIT系の会社役員を4年、独立して4年目になります。クライアントワークでは「こうしたい」を技術で形にすることを専門としていますが、最近は個人開発でAIを「無駄に」使った社会風刺的なサービスも作っています。

https://zenn.dev/5naokichi/articles/8f9446a136a874

完成品・デモ

https://hone-gumi.com/test/

実装した機能:

  • 30問の選択式質問
  • リアルタイム進捗表示
  • 自動スコア計算
  • 詳細結果解説
  • 累計利用回数カウンター
  • レスポンシブ対応

技術構成

フロントエンド:

  • HTML5(セマンティックなマークアップ)
  • CSS3(フレックスボックス、グラデーション)
  • Vanilla JavaScript(軽量実装)

バックエンド:

  • WordPress(CMS基盤)
  • PHP(AJAX処理)
  • WordPress AJAX API(利用回数管理)

実装手順

1. 基本HTML構造

診断テストの基本構造です:

index.html
<div class="brain-test-container">
    <div id="progress-bar" class="progress-bar"></div>
    <h2>男脳・女脳 診断テスト</h2>
    
    <!-- 累計使用回数表示 -->
    <div class="usage-stats">
        <p class="total-usage">累計診断回数: <span id="total-count">読み込み中...</span>回</p>
    </div>
    
    <!-- 性別選択 -->
    <div class="gender-select question">
        <h3>性別を選んでください</h3>
        <label><input type="radio" name="gender" value="male" required> 男性</label>
        <label><input type="radio" name="gender" value="female" required> 女性</label>
    </div>
    
    <!-- 質問コンテナ -->
    <div id="questions-container">
        <!-- 30問の質問がここに動的に生成される -->
    </div>
    
    <!-- 結果表示エリア -->
    <div id="score-display" class="score-display">
        <h3>テスト結果</h3>
        <p>合計点: <span id="score">0</span>点</p>
        <div id="score-explanation">
            <!-- 詳細解説がここに表示される -->
        </div>
    </div>
    
    <button onclick="calculateScore()" class="score-button">採点する</button>
</div>

2. スタイリング

レスポンシブ対応とユーザビリティを重視したCSS:

style.css
.brain-test-container {
    max-width: 800px;
    margin: 0 auto;
    font-family: sans-serif;
    padding: 15px;
}

/* 累計使用回数表示のスタイル */
.usage-stats {
    text-align: center;
    margin: 15px 0 25px 0;
    padding: 12px;
    background: linear-gradient(135deg, #f3e7ff, #e8d5ff);
    border-radius: 8px;
    border: 2px solid #9b59b6;
    box-shadow: 0 2px 8px rgba(155, 89, 182, 0.1);
}

.question {
    margin-bottom: 20px;
    padding: 15px;
    border: 1px solid #ddd;
    border-radius: 8px;
    background: #fff;
}

.question label {
    display: block;
    margin: 10px 0;
    padding: 10px;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.2s ease;
}

.question label:hover {
    background: #f5f5f5;
}

/* 固定ボタンのスタイル */
.score-button {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    padding: 15px 30px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 25px;
    font-size: 16px;
    cursor: pointer;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    z-index: 1000;
    transition: background-color 0.3s ease;
}

.score-button:hover {
    background-color: #45a049;
}

/* プログレスバー */
.progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    width: 0%;
    height: 4px;
    background: #4CAF50;
    transition: width 0.3s ease;
    z-index: 1000;
}

/* レスポンシブ対応 */
@media (max-width: 768px) {
    .brain-test-container {
        padding: 10px;
    }
    
    .score-button {
        padding: 12px 24px;
        font-size: 14px;
    }
}

3. JavaScriptロジック

スコア計算と結果表示の核となるJavaScript:

script.js
// ページ読み込み時に累計カウント表示
document.addEventListener('DOMContentLoaded', function() {
    loadCurrentCount();
    generateQuestions();
    updateProgressBar();
});

// 質問を動的に生成
function generateQuestions() {
    const questions = [
        {
            id: 1,
            text: "地図を読むのが得意ですか?",
            options: [
                { value: "a", text: "とても得意" },
                { value: "b", text: "普通" },
                { value: "c", text: "苦手" }
            ]
        },
        // 実際は30問の質問データ
        // ...
    ];
    
    const container = document.getElementById('questions-container');
    questions.forEach(question => {
        const questionDiv = createQuestionElement(question);
        container.appendChild(questionDiv);
    });
}

// 質問要素を作成
function createQuestionElement(question) {
    const div = document.createElement('div');
    div.className = 'question';
    div.innerHTML = `
        <h3>Q${question.id}. ${question.text}</h3>
        ${question.options.map(option => `
            <label>
                <input type="radio" name="q${question.id}" value="${option.value}" onchange="updateProgressBar()">
                ${option.text}
            </label>
        `).join('')}
    `;
    return div;
}

// プログレスバーを更新
function updateProgressBar() {
    const totalQuestions = 30;
    let answeredQuestions = 0;
    
    for(let i = 1; i <= totalQuestions; i++) {
        const radios = document.getElementsByName('q' + i);
        for(let j = 0; j < radios.length; j++) {
            if(radios[j].checked) {
                answeredQuestions++;
                break;
            }
        }
    }
    
    const progress = (answeredQuestions / totalQuestions) * 100;
    document.getElementById('progress-bar').style.width = progress + '%';
}

// 累計カウント数を取得して表示
function loadCurrentCount() {
    fetch('/wp-admin/admin-ajax.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: 'action=brain_test_get_count'
    })
    .then(response => response.text())
    .then(count => {
        document.getElementById('total-count').textContent = count;
    })
    .catch(error => {
        console.error('カウント取得エラー:', error);
        document.getElementById('total-count').textContent = '0';
    });
}

// カウンターを更新する関数
function updateUsageCounter() {
    fetch('/wp-admin/admin-ajax.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: 'action=brain_test_counter'
    })
    .then(response => response.text())
    .then(count => {
        document.getElementById('total-count').textContent = count;
        // カウント更新時のアニメーション効果
        const countElement = document.getElementById('total-count');
        countElement.style.transform = 'scale(1.2)';
        countElement.style.transition = 'transform 0.3s ease';
        setTimeout(() => {
            countElement.style.transform = 'scale(1)';
        }, 300);
    })
    .catch(error => {
        console.error('カウンター更新エラー:', error);
    });
}

// メインのスコア計算関数
function calculateScore() {
    let score = 0;
    let answeredQuestions = 0;
    
    // 全30問をループで処理
    for(let i = 1; i <= 30; i++) {
        const radios = document.getElementsByName('q' + i);
        let answered = false;
        
        for(let j = 0; j < radios.length; j++) {
            if(radios[j].checked) {
                answered = true;
                // スコア計算ロジック
                switch(radios[j].value) {
                    case 'a':
                        score += 10;
                        break;
                    case 'b':
                        score += 5;
                        break;
                    case 'c':
                        score -= 5;
                        break;
                }
            }
        }
        
        if(answered) {
            answeredQuestions++;
        }
    }
    
    // 全問回答チェック
    if(answeredQuestions < 30) {
        alert('すべての質問に回答してください。');
        return;
    }
    
    // 採点成功時にカウンターを更新
    updateUsageCounter();
    
    // 結果表示
    displayResults(score, answeredQuestions);
}

// 結果表示関数
function displayResults(score, answeredQuestions) {
    const display = document.getElementById('score-display');
    const scoreSpan = document.getElementById('score');
    const explanation = document.getElementById('score-explanation');
    
    // 脳タイプの判定ロジック
    let typeText = "";
    let detailText = "";
    
    if (score < 150) {
        typeText = "あなたは【男脳タイプ】です。";
        detailText = "論理的思考が得意で、空間認識能力に長けています。問題解決において体系的なアプローチを取る傾向があります。";
    } else if (score <= 180) {
        typeText = "あなたは【バランス型】です。";
        detailText = "男脳・女脳の特徴をバランス良く持っています。状況に応じて柔軟な思考パターンを使い分けることができます。";
    } else {
        typeText = "あなたは【女脳タイプ】です。";
        detailText = "感情豊かで直感力に優れています。コミュニケーション能力が高く、複数のことを同時に処理するのが得意です。";
    }
    
    display.style.display = 'block';
    scoreSpan.textContent = score;
    explanation.innerHTML = `
        <h4>${typeText}</h4>
        <p>${detailText}</p>
    `;
    
    // 結果表示位置にスムーススクロール
    display.scrollIntoView({ behavior: 'smooth' });
}

4. WordPress側のPHP実装

functions.phpに以下を追加して、AJAX通信を処理します:

functions.php
// AJAX ハンドラー関数 - カウンター増加
function brain_test_counter_increment() {
    $count = get_option('brain_test_usage_count', 0);
    $count++;
    update_option('brain_test_usage_count', $count);
    wp_die($count);
}

// AJAX ハンドラー関数 - カウンター取得
function brain_test_counter_get() {
    $count = get_option('brain_test_usage_count', 0);
    wp_die($count);
}

// AJAX アクション登録(ログインユーザー用)
add_action('wp_ajax_brain_test_counter', 'brain_test_counter_increment');
add_action('wp_ajax_brain_test_get_count', 'brain_test_counter_get');

// AJAX アクション登録(非ログインユーザー用)
add_action('wp_ajax_nopriv_brain_test_counter', 'brain_test_counter_increment');
add_action('wp_ajax_nopriv_brain_test_get_count', 'brain_test_counter_get');

工夫したポイント

1. ユーザーエクスペリエンスの最適化

固定ボタンによる操作性向上:
30問すべて回答後、画面をスクロールすることなく採点できるよう、採点ボタンを画面下部に固定配置しました。

視覚的フィードバック:

  • リアルタイムプログレスバーによる進捗表示
  • 利用回数更新時のアニメーション効果
  • ホバー効果による直感的な操作感

2. WordPress AJAX連携

データ永続化の実装:
WordPressのwp_optionsテーブルを使用してカウンターデータを永続化しました。テーマ変更やプラグイン更新の影響を受けない安全な実装です。

エラーハンドリング:
ネットワークエラーやサーバーエラーが発生した場合でも、診断機能自体は継続して利用できるよう設計しています。

3. パフォーマンス最適化

Vanilla JavaScript採用:
jQueryを使わずにVanilla JavaScriptで実装し、軽量化を図りました。外部ライブラリ依存を避けることで、読み込み速度も向上しています。

非同期処理:
カウンター更新処理を非同期で実行し、ユーザーの診断体験を阻害しないよう配慮しました。

苦労した点

レスポンシブ対応

特にスマートフォンでの操作性確保が課題でした。固定ボタンの配置やタップ領域の最適化に工夫が必要でした。

スコア計算ロジックの実装

書籍の診断基準を正確にWebアプリに反映するため、スコア計算アルゴリズムの調整に時間をかけました。

WordPressとの連携

AJAX通信での適切なエラーハンドリングと、セキュリティを考慮したPHP実装に注意が必要でした。

まとめ

書籍の診断テストをWeb化することで、紙ベースでは実現できなかった以下の価値を提供できました:

  • 自動採点機能による即座の結果表示
  • リアルタイムカウンターによる社会的証明の可視化
  • レスポンシブ対応による多デバイス対応
  • 進捗表示によるユーザビリティ向上

単純な機能ながら、ユーザビリティとエンターテイメント性を両立させた実装となりました。

WordPressとJavaScriptの連携パターンとしても参考になる事例だと思います。診断系コンテンツやインタラクティブなWebアプリケーション開発の参考になれば幸いです。

参考リンク

参考書籍

📚 話を聞かない男、地図が読めない女
アラン・ピーズ (著), バーバラ・ピーズ (著), 藤井 留美 (翻訳)
🛒 Amazonで詳細を見る

この書籍の診断テストを元に今回のWebアプリケーションを開発しました。男女の脳の違いについて科学的な観点から解説されており、非常に興味深い内容です。

技術関連

Discussion