📝

【ClaudeCode開発】文字数チェッカー - シンプルなリアルタイム文字カウントツール

に公開

はじめに

「毎日使うものだからこそシンプルに」という思想で、文字数カウント機能のみに特化したWebツールを開発しました。リリース後にユーザーから文字数カウント方式についてのコメントをいただき、Unicode文字数方式への改善も行いました。YouTube台本制作で毎日他社の文字数チェックサービスを使用する中で感じた「余計な機能による複雑さ」を解決しながら、正確な文字カウントを実現するツールです。

本記事では、リアルタイム文字カウント、3パターンの文字数表示、プライバシー配慮のクライアントサイド設計など、シンプルながら実用的なツールの構築手順を解説します。

本記事について

今回の開発および記事執筆は、Claude Code(Anthropic社のAI)との協業で進めました。AI時代の新しい開発スタイルの実践例として参考になれば幸いです。

自己紹介

ホネグミ代表、応用情報技術者の資格を持つエンジニア×マーケターです。これまでIT系の会社役員を4年、独立して4年目になります。クライアントワークでは「こうしたい」を技術で形にすることを専門としていますが、最近は思想駆動型サービス開発の第一人者として、AIを活用した様々なサービス開発を続けています。

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

サービス概要

https://hone-gumi.com/text-checker/

主要機能の特徴

  • Unicode文字数方式: 特殊文字も他のWebサービスと一致する正確なカウント
  • リアルタイム文字カウント: 入力と同時に瞬時に文字数表示
  • 3種類の計算パターン: 改行含む、改行除く、改行・空白除く
  • 完全プライバシー保護: 入力データのサーバー送信なし
  • 単一ファイル構成: 外部ライブラリ依存なしの軽量実装

技術スタック

フロントエンド構成

単一HTMLファイル
├── HTML5 (Semantic Markup)
├── CSS3 (Grid Layout + Responsive)
└── JavaScript (ES5準拠, Vanilla JS)

採用した技術選択の理由

単一HTMLファイル構成

  • 配布・組み込みの容易性
  • HTTPリクエスト数の最小化による高速ローディング
  • 外部依存関係の完全排除

ES5準拠JavaScript

  • 幅広いブラウザ互換性
  • トランスパイル不要のシンプル実装
  • フレームワーク依存なしの軽量性

クライアントサイド完結型

  • ユーザーデータのプライバシー完全保護
  • サーバーコスト削減
  • オフライン環境での動作保証

実装手順

1. HTMLの基本構造設計

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="文字数チェッカー【無料】でテキストの文字数をリアルタイムカウント!">
    <title>文字数チェッカー</title>
</head>
<body>
    <div class="character-counter-wrapper">
        <div class="character-counter">
            <h2>📝 文字数チェッカー</h2>
            
            <!-- テキスト入力エリア -->
            <div class="input-section">
                <label for="textInput">カウントしたいテキストを入力または貼り付けてください</label>
                <textarea 
                    id="textInput" 
                    class="text-input" 
                    oninput="countCharacters()"
                    placeholder="ここにテキストを入力してください..."
                ></textarea>
            </div>
            
            <!-- リアルタイム結果表示 -->
            <div class="results">
                <div class="result-item">
                    <span class="result-number" id="totalCount">0</span>
                    <div class="result-label">文字数<br>(改行含む)</div>
                </div>
                <div class="result-item">
                    <span class="result-number" id="noBreakCount">0</span>
                    <div class="result-label">改行を除いた<br>文字数</div>
                </div>
                <div class="result-item">
                    <span class="result-number" id="noSpaceCount">0</span>
                    <div class="result-label">改行・空白を除いた<br>文字数</div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

2. 核心の文字カウントアルゴリズム

function countCharacters() {
    // テキストエリアの値を取得
    const text = document.getElementById('textInput').value;
    
    // Unicode文字数方式でカウント(スプレッド演算子使用)
    
    // パターン1: 改行含む文字数(基本)- Unicode文字数でカウント
    const totalCount = [...text].length;
    
    // パターン2: 改行除く文字数(文章の実質的長さ)
    // \r?\n で改行文字(CRLF/LF)を除去
    const noBreakCount = [...text.replace(/\r?\n/g, '')].length;
    
    // パターン3: 改行・空白除く文字数(純文字数)
    // [\r\n\s\t ] で改行・半角空白・タブ・全角空白を除去
    const noSpaceCount = [...text.replace(/[\r\n\s\t ]/g, '')].length;
    
    // UIに結果を反映(3桁区切り表示)
    document.getElementById('totalCount').textContent = totalCount.toLocaleString();
    document.getElementById('noBreakCount').textContent = noBreakCount.toLocaleString();
    document.getElementById('noSpaceCount').textContent = noSpaceCount.toLocaleString();
}

Unicode文字数方式への改善経緯

ユーザーコメントからの気づき

リリース後、Qiitaで以下のようなコメントをいただきました:

葛 1文字、葛󠄀 3文字、𩸽 2文字、👩‍👩‍👦‍👦 11文字という結果になりましたが仕様ですか?

このコメントで、従来のtext.length方式(UTF-16コードユニット数)では特殊文字が正確にカウントできないことが判明。

他のWebサービスと比較した結果、「1、2、1、7文字」というUnicode文字数方式の結果が一般的であることが確認できました。

改善実装の技術的詳細

// 改善前(UTF-16コードユニット数)
const oldMethod = text.length;

// 改善後(Unicode文字数)
const newMethod = [...text].length;

// 異体字セレクタ付き文字の例
const testChar = '葛󠄀'; // 葛 + 異体字セレクタ

console.log(testChar.length);          // 3(UTF-16コードユニット)
console.log([...testChar].length);     // 2(Unicode文字数)

// サロゲートペア文字の例
const surrogatePair = '𩸽'; // 鱼偏の漢字

console.log(surrogatePair.length);     // 2(UTF-16コードユニット)
console.log([...surrogatePair].length); // 1(Unicode文字数)

3. レスポンシブCSS実装

/* メインコンテナ */
.character-counter-wrapper {
    font-family: 'Helvetica Neue', Arial, sans-serif;
    background: #f5f7fa;
    padding: 20px;
    min-height: 100vh;
}

/* カードデザイン */
.character-counter {
    max-width: 800px;
    margin: 0 auto;
    background: white;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
    padding: 20px;
}

/* テキストエリア */
.text-input {
    width: 100%;
    min-height: 300px;
    padding: 15px;
    border: 2px solid #e0e6ed;
    border-radius: 10px;
    font-size: 16px;
    font-family: inherit;
    resize: vertical;
    transition: all 0.3s ease;
    background: #f8f9fa;
}

.text-input:focus {
    outline: none;
    border-color: #2c5aa0;
    background: white;
    box-shadow: 0 0 0 3px rgba(44, 90, 160, 0.1);
}

/* 結果表示グリッド */
.results {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 15px;
    margin: 30px 0;
}

/* 結果カード */
.result-item {
    background: linear-gradient(135deg, #2c5aa0 0%, #4a7bc8 100%);
    color: white;
    padding: 20px;
    border-radius: 10px;
    text-align: center;
    box-shadow: 0 4px 12px rgba(44, 90, 160, 0.2);
    transition: transform 0.3s ease;
}

.result-item:hover {
    transform: translateY(-3px);
}

.result-number {
    font-size: 2.2rem;
    font-weight: bold;
    display: block;
    margin-bottom: 8px;
}

/* モバイル対応 */
@media (max-width: 600px) {
    .character-counter-wrapper {
        padding: 10px;
    }
    
    .character-counter {
        padding: 15px;
    }
    
    .text-input {
        min-height: 200px;
        padding: 12px;
    }
    
    .results {
        grid-template-columns: 1fr;
        gap: 10px;
    }
    
    .result-number {
        font-size: 1.8rem;
    }
}

工夫したポイント・苦労した点

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

リアルタイム処理の軽量化

// ❌ 重い処理の例(避けるべき)
function heavyCountCharacters() {
    const text = document.getElementById('textInput').value;
    
    // 複雑な処理でUIブロッキングが発生
    let count = 0;
    for (let i = 0; i < text.length; i++) {
        // 文字種別判定などの重い処理
        if (isValidCharacter(text[i])) count++;
    }
}

// ✅ 軽量な処理(推奨)
function lightCountCharacters() {
    const text = document.getElementById('textInput').value;
    
    // シンプルな文字列処理で高速動作
    const totalCount = text.length;
    const noBreakCount = text.replace(/\r?\n/g, '').length;
    const noSpaceCount = text.replace(/[\r\n\s\t ]/g, '').length;
}

2. 日本語環境特有の対応

全角文字・全角空白の適切な処理

// 日本語特有の全角空白( )にも対応
const noSpaceCount = text.replace(/[\r\n\s\t ]/g, '').length;
//                                        ↑ 全角空白

// 半角・全角を区別しない文字数カウント
// → 直感的で理解しやすい結果
const totalCount = text.length; // 'あ' も 'a' も 1文字として計算

3. ブラウザ互換性の確保

ES5準拠による古いブラウザ対応

// ❌ ES6以降の記法(避ける)
const countCharacters = () => {
    const text = document.getElementById('textInput').value;
    document.getElementById('totalCount').textContent = `${text.length}`;
};

// ✅ ES5準拠(推奨)
function countCharacters() {
    var text = document.getElementById('textInput').value;
    document.getElementById('totalCount').textContent = text.length.toString();
}

4. プライバシー設計の実装

完全クライアントサイド処理

// ✅ 安全な実装
function countCharacters() {
    // ローカル変数として処理
    var text = document.getElementById('textInput').value;
    
    // サーバー送信なし、外部API呼び出しなし
    var totalCount = text.length;
    
    // DOM更新のみ、ネットワーク通信なし
    document.getElementById('totalCount').textContent = totalCount;
}

// ❌ 避けるべき実装例
function unsafeCountCharacters() {
    var text = document.getElementById('textInput').value;
    
    // NGパターン: サーバーに送信
    fetch('/api/count', {
        method: 'POST',
        body: JSON.stringify({text: text}) // ユーザーデータが外部送信される
    });
}

アーキテクチャ設計の考察

シンプル思想の実装方針

機能の厳選基準

  1. 毎日使用する機能か?
  2. 代替手段がない機能か?
  3. 理解しやすい機能か?
// 採用した機能(必要最小限)
- 改行含む文字数    → 基本中の基本
- 改行除く文字数    → 文章の実質的長さ
- 空白除く文字数    → 純粋な文字数

// 意図的に除外した機能(複雑性の排除)
- バイト数計算     → 使用頻度低
- 原稿用紙換算     → 使用頻度低
- 文字種別分析     → 複雑性増大
- 文字数履歴       → データ管理の複雑化

パフォーマンス設計

単一ファイル構成による最適化

従来の構成:
├── index.html     (HTMLリクエスト)
├── style.css      (CSSリクエスト)
├── script.js      (JavaScriptリクエスト)
└── library.js     (ライブラリリクエスト)
合計: 4回のHTTPリクエスト

最適化後:
└── index.html (全て内包)
合計: 1回のHTTPリクエスト

効果: 初期ローディング時間を約75%短縮

まとめ

文字数チェッカーの開発を通じて得られた設計思想:

1. Unicode文字数方式の重要性

特殊文字の正確な処理により、他のWebサービスと一致する信頼性の高いツールを実現

2. ユーザーフィードバックの価値

実際の利用者からの指摘により、より正確で実用的なツールへと進化

3. シンプル設計の価値

「毎日使うツール」こそ、余計な機能を排除したシンプル設計が最も効果的

4. プライバシーファーストの重要性

クライアントサイド完結型により、ユーザーの安心感と技術的シンプルさを同時実現

5. パフォーマンス最適化の効果

単一ファイル構成による高速ローディングで、日常使用のストレスを大幅軽減

技術的複雑性を排除しつつ、Unicode文字数方式による正確な計測と、日常的な利用シーンで実用性の高いツールを開発できました。ユーザーからのフィードバックを活かした継続的改善が、より価値あるサービスへと発展させる重要な要素であることを実感しました。


実際のサービス

https://hone-gumi.com/text-checker/

免責事項: 本ツールはシンプルな文字数カウント機能の提供を目的としています。文字数計算結果は参考値であり、特定のプラットフォームでの文字数制限とは異なる場合があります。

Discussion