0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Webで画像をぼかして保存できる無料ツール「Bokashi Tool」を作りました!

Posted at

こんにちは、tampopo256です!

今回は、ブラウザだけで手軽に画像にぼかし加工をかけて保存できるWebツールを作ったのでご紹介します。

📍 サイトURL

👉 https://bokashi.tokai.club/

🎨 ツールの特徴

  • 完全無料・インストール不要
  • 画像をアップロードして、スライダーでぼかし強度を調整
  • ブラウザ上でリアルタイムプレビュー
  • 加工後はワンクリックで保存可能(JPG / PNG)
  • スマホ対応

🛠️ 開発背景

以下のようなシーンで使いたくて作りました。

  • SNS投稿前に情報を隠したいとき
  • 手元にPhotoshopなどがないとき
  • スマホやPCブラウザだけでサクッと加工したいとき

👨‍💻 使用技術・構成

  • HTML / CSS / JavaScript(バニラJS)
  • Canvas APIによる画像処理
  • レスポンシブ対応(スマホOK)
  • デプロイ先:Tokai Club ドメインで自前ホスティング

💻 ソースコード全文

以下が今回のHTML + CSS + JSの全文です。

コードを見る
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>画像ぼかしツール</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
        }

        .container {
            max-width: 900px;
            margin: 0 auto;
            padding: 20px;
            flex: 1;
        }

        .header {
            text-align: center;
            margin-bottom: 40px;
        }

        .title {
            font-size: 2.5rem;
            font-weight: 700;
            color: #2c3e50;
            margin-bottom: 10px;
            text-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .subtitle {
            font-size: 1.1rem;
            color: #7f8c8d;
            line-height: 1.6;
            max-width: 600px;
            margin: 0 auto;
        }

        .main-content {
            background: white;
            border-radius: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
            padding: 40px;
            margin-bottom: 40px;
        }

        .upload-area {
            border: 3px dashed #bdc3c7;
            border-radius: 15px;
            padding: 60px 20px;
            text-align: center;
            margin-bottom: 30px;
            transition: all 0.3s ease;
            background: #f8f9fa;
        }

        .upload-area:hover {
            border-color: #3498db;
            background: #e3f2fd;
        }

        .upload-area.dragover {
            border-color: #2980b9;
            background: #bbdefb;
            transform: scale(1.02);
        }

        .upload-icon {
            font-size: 3rem;
            color: #95a5a6;
            margin-bottom: 20px;
        }

        .upload-text {
            font-size: 1.2rem;
            color: #7f8c8d;
            margin-bottom: 15px;
        }

        .upload-subtext {
            font-size: 0.9rem;
            color: #95a5a6;
        }

        .file-input {
            display: none;
        }

        .upload-btn {
            background: linear-gradient(135deg, #3498db, #2980b9);
            color: white;
            border: none;
            padding: 12px 30px;
            border-radius: 25px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
        }

        .upload-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
        }

        .controls {
            display: none;
            margin-bottom: 30px;
        }

        .control-group {
            margin-bottom: 25px;
        }

        .control-label {
            display: block;
            font-weight: 600;
            color: #2c3e50;
            margin-bottom: 10px;
            font-size: 1.1rem;
        }

        .blur-control {
            display: flex;
            align-items: center;
            gap: 15px;
            background: #f8f9fa;
            padding: 20px;
            border-radius: 12px;
        }

        .blur-slider {
            flex: 1;
            height: 8px;
            border-radius: 4px;
            background: #e0e0e0;
            outline: none;
            cursor: pointer;
        }

        .blur-slider::-webkit-slider-thumb {
            appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #3498db;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0,0,0,0.2);
        }

        .blur-value {
            min-width: 60px;
            font-weight: 600;
            color: #2c3e50;
            font-size: 1.1rem;
        }

        .preview-area {
            display: none;
            margin-bottom: 30px;
        }

        .preview-title {
            text-align: center;
            margin-bottom: 20px;
            font-weight: 600;
            color: #2c3e50;
            font-size: 1.1rem;
        }

        .preview-container {
            text-align: center;
            background: #f8f9fa;
            border-radius: 15px;
            padding: 20px;
            min-height: 300px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .preview-image {
            max-width: 100%;
            max-height: 400px;
            border-radius: 10px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
        }

        .action-buttons {
            display: none;
            justify-content: center;
        }

        .btn {
            padding: 12px 30px;
            border: none;
            border-radius: 25px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            min-width: 120px;
        }

        .btn-primary {
            background: linear-gradient(135deg, #e74c3c, #c0392b);
            color: white;
            box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(231, 76, 60, 0.4);
        }

        .btn-secondary {
            background: linear-gradient(135deg, #27ae60, #219a52);
            color: white;
            box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
        }

        .btn-secondary:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
        }

        .format-select {
            display: none;
            align-items: center;
            gap: 10px;
            margin-top: 15px;
            justify-content: center;
        }

        .format-select select {
            padding: 8px 15px;
            border: 2px solid #bdc3c7;
            border-radius: 20px;
            background: white;
            font-weight: 600;
            color: #2c3e50;
        }

        .footer {
            text-align: center;
            padding: 20px;
            color: #7f8c8d;
            font-size: 0.9rem;
        }

        .footer a {
            color: #3498db;
            text-decoration: none;
            font-weight: 600;
        }

        @media (max-width: 768px) {
            .main-content {
                padding: 20px;
                margin: 0 10px 20px;
            }
            
            .title {
                font-size: 2rem;
            }
            
            .action-buttons {
                flex-direction: column;
                align-items: center;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1 class="title">画像ぼかしツール</h1>
            <p class="subtitle">
                画像をアップロードして、お好みの強度でぼかし効果を適用できます。<br>
                操作は簡単で、プレビューを確認しながら調整可能です。
            </p>
        </div>

        <div class="main-content">
            <div class="upload-area" id="uploadArea">
                <div class="upload-icon">📷</div>
                <div class="upload-text">画像をドラッグ&ドロップ</div>
                <div class="upload-subtext">または</div>
                <br>
                <button class="upload-btn" onclick="document.getElementById('fileInput').click()">
                    ファイルを選択
                </button>
                <input type="file" id="fileInput" class="file-input" accept="image/*">
            </div>

            <div class="controls" id="controls">
                <div class="control-group">
                    <label class="control-label">ぼかし強度</label>
                    <div class="blur-control">
                        <input type="range" id="blurSlider" class="blur-slider" min="0" max="20" value="0" step="0.5">
                        <span class="blur-value"><span id="blurValue">0</span>px</span>
                    </div>
                </div>
            </div>

            <div class="preview-area" id="previewArea">
                <div class="preview-title">プレビュー</div>
                <div class="preview-container">
                    <img id="previewImage" class="preview-image" alt="プレビュー">
                </div>
            </div>

            <div class="action-buttons" id="actionButtons">
                <button class="btn btn-secondary" id="downloadBtn">保存</button>
            </div>

            <div class="format-select" id="formatSelect">
                <span>保存形式:</span>
                <select id="formatDropdown">
                    <option value="png">PNG</option>
                    <option value="jpg">JPG</option>
                </select>
            </div>
        </div>
    </div>

    <div class="footer">
        <p>&copy; 2025 画像ぼかしツール</p>
    </div>

    <script>
        let originalImage = null;
        let processedImage = null;
        let currentBlur = 0;

        // DOM要素の取得
        const uploadArea = document.getElementById('uploadArea');
        const fileInput = document.getElementById('fileInput');
        const controls = document.getElementById('controls');
        const previewArea = document.getElementById('previewArea');
        const actionButtons = document.getElementById('actionButtons');
        const formatSelect = document.getElementById('formatSelect');
        const blurSlider = document.getElementById('blurSlider');
        const blurValue = document.getElementById('blurValue');
        const previewImage = document.getElementById('previewImage');
        const downloadBtn = document.getElementById('downloadBtn');
        const formatDropdown = document.getElementById('formatDropdown');

        // ファイルアップロード処理
        fileInput.addEventListener('change', handleFileSelect);
        uploadArea.addEventListener('dragover', handleDragOver);
        uploadArea.addEventListener('dragleave', handleDragLeave);
        uploadArea.addEventListener('drop', handleDrop);

        // スライダーイベント
        blurSlider.addEventListener('input', function() {
            currentBlur = parseFloat(this.value);
            blurValue.textContent = currentBlur;
            if (originalImage) {
                updatePreview();
                // 自動的にぼかし効果を適用
                applyBlur();
            }
        });

        // ボタンイベント
        downloadBtn.addEventListener('click', downloadImage);

        function handleFileSelect(e) {
            const file = e.target.files[0];
            if (file && file.type.startsWith('image/')) {
                loadImage(file);
            }
        }

        function handleDragOver(e) {
            e.preventDefault();
            uploadArea.classList.add('dragover');
        }

        function handleDragLeave(e) {
            e.preventDefault();
            uploadArea.classList.remove('dragover');
        }

        function handleDrop(e) {
            e.preventDefault();
            uploadArea.classList.remove('dragover');
            const file = e.dataTransfer.files[0];
            if (file && file.type.startsWith('image/')) {
                loadImage(file);
            }
        }

        function loadImage(file) {
            const reader = new FileReader();
            reader.onload = function(e) {
                originalImage = new Image();
                originalImage.onload = function() {
                    previewImage.src = e.target.result;
                    showControls();
                    updatePreview(); // 初期表示で処理後画像を表示
                };
                originalImage.src = e.target.result;
            };
            reader.readAsDataURL(file);
        }

        function showControls() {
            controls.style.display = 'block';
            previewArea.style.display = 'block';
            actionButtons.style.display = 'flex';
            formatSelect.style.display = 'flex';
        }

        function updatePreview() {
            if (currentBlur > 0) {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = originalImage.width;
                canvas.height = originalImage.height;
                
                ctx.filter = `blur(${currentBlur}px)`;
                ctx.drawImage(originalImage, 0, 0);
                
                previewImage.src = canvas.toDataURL();
            } else {
                previewImage.src = originalImage.src;
            }
        }

        function applyBlur() {
            if (currentBlur > 0) {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = originalImage.width;
                canvas.height = originalImage.height;
                
                ctx.filter = `blur(${currentBlur}px)`;
                ctx.drawImage(originalImage, 0, 0);
                
                processedImage = canvas;
            } else {
                // ぼかしが0の場合は元画像を使用
                processedImage = null;
            }
        }

        function downloadImage() {
            if (!originalImage) return;
            
            const canvas = processedImage || document.createElement('canvas');
            if (!processedImage) {
                const ctx = canvas.getContext('2d');
                canvas.width = originalImage.width;
                canvas.height = originalImage.height;
                ctx.filter = `blur(${currentBlur}px)`;
                ctx.drawImage(originalImage, 0, 0);
            }
            
            const format = formatDropdown.value;
            const mimeType = format === 'jpg' ? 'image/jpeg' : 'image/png';
            const quality = format === 'jpg' ? 0.9 : 1.0;
            
            canvas.toBlob(function(blob) {
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `blurred_image.${format}`;
                a.click();
                URL.revokeObjectURL(url);
            }, mimeType, quality);
        }
    </script>
</body>
</html>

📸 使用イメージ

  1. ファイルをドラッグ&ドロップ or ファイル選択
  2. スライダーでぼかし量を調整
  3. プレビューを確認
  4. 「保存」ボタンでダウンロード!

スマホでもこんな感じで動きます👇
(スクリーンショットは省略。必要なら後から追加してください)

🚀 今後追加したい機能(予定)

  • モザイク処理への切り替え
  • 部分ぼかし(範囲選択)
  • 複数画像一括処理

✅ 実際のサイトはこちら

👉 https://bokashi.tokai.club/

🙏 最後に

「もっとこうして欲しい」「この機能つけて!」などあればQiitaコメントかX(旧Twitter)@kurosangurasuまで気軽にどうぞ!

🎉 関連ツールもぜひ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?