Web開発 - ラバーダック・デバッグをするためのウェブアプリを作ってみた
初めに
ラバーダッグ・デバッグをブラウザ上でするためのアプリを作成したので、記事にまとめました。
ラバーダッグ・デバッグ(Rubber duck debugging)とは
独り言でコードを説明する過程で解決策を思いつくというものです。プログラマーがラバー・ダック(アヒルちゃん)を持ち歩きアヒルちゃんに向かってコードを1行ずつ説明することによりデバッグを行うという話が由来です。アンドリュー・ハントとデビッド・トーマスの共著による "The Pragmatic Programmer" という本で紹介されたもので、無生物を用いることにより、プログラマーは、他人を煩わせることなく目的を達成できます。
完成図
簡単な機能の紹介
- ユーザーの入力を受け付け、その入力を画面上部に表示する
- ユーザーの入力を表示しきれなくなったら、スクロールできるようにする
- ユーザーが入力したら、あひるちゃんがうなずき、「クワッ」と鳴く
構成
ファイル | 説明 |
---|---|
index.html | HTML |
styles.css | CSS |
script.js | JavaScript |
furo-duchy.png | あひるちゃんの画像(いらすとや) |
MonomaniacOne-Regular.ttf | フォント |
コード全文
HTML
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="styles.css"> <title>Rubber Duck Debugging</title> </head> <header> <h1>Rubber Duck Debugging</h1> <p>ラバーダック・デバッグをウェブ上で体験するためにアプリ。</p> </header> <body> <div class="upper-half"> <div class="scroll-container"> <div class="input-text-list" id="inputTexts"></div> </div> </div> <div class="lower-half"> <div class="rubber-duck-container"> <div class="rubber-duck" id="rubberDuck"> <div class="speech-bubble" id="speechBubble"></div> </div> </div> <div class="user-input-container"> <input type="text" id="userInput" placeholder="何か困ったことを入力..."> <button onclick="handleUserInput()">ユーザー入力を処理</button> </div> </div> <script src="script.js"></script> </body> </html>
CSS
@font-face { font-family: 'Monomaniac One'; src: url('MonomaniacOne-Regular.ttf') format('truetype'); } header { text-align: center; padding: 10px; background-color: #ffeb3b; color: #FFF; font-family: 'Monomaniac One', sans-serif; } header h1 { font-size: 48px; margin: 0; text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; } header p { font-size: 24px; margin: 0; text-shadow: 1.5px 1.5px 0 #000, -0.5px -0.5px 0 #000, 0.5px -0.5px 0 #000, -0.5px 0.5px 0 #000, 0.5px 0.5px 0 #000; } body { display: flex; flex-direction: column; height: 100vh; margin: 0; overflow-y: scroll; } .upper-half { text-align: center; background-color: #b3d7f7; flex-shrink: 1; } .scroll-container { overflow-y: scroll; background-color: #ffffff; margin: 10px; border-radius: 5px; height: calc(38vh); } .input-text-list { display: flex; flex-direction: column; align-items: flex-start; padding: 5px; } .input-text-item { background-color: #f0f0f0; padding: 5px; margin: 5px 0; border-radius: 5px; } .rubber-duck-container { order: 1; display: flex; align-items: center; justify-content: center; position: relative; } .rubber-duck { width: 400px; height: 325px; background-image: url('furo_ducky.png'); /* rubber-duckの画像 */ background-size: cover; transition: margin-right 0.3s ease-in-out; } .speech-bubble { font-family: 'Monomaniac One'; font-size: xx-large; background-color: #fff; padding: 10px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); display: none; position: absolute; top: 50%; left: 100%; transform: translate(-50%, -50%); } .lower-half { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; flex-shrink: 1; } .user-input-container { order: 2; display: flex; flex-direction: column; align-items: center; padding: 10px; background-color: #f0f0f0; border-radius: 5px; } #userInput { margin-top: 5px; padding: 10px; width: 100%; max-width: 400px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 3px; } #userInput::placeholder { color: #999; } button { margin-top: 5px; padding: 10px; width: 100%; max-width: 400px; box-sizing: border-box; background-color: #4caf50; color: #fff; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #45a049; } /* Media query for screens larger than 600px */ @media screen and (min-width: 601px) { #userInput { width: 600px; } } /* うなずくアニメーションを適用するクラス */ .nodding { animation: nodAnimation 0.5s ease-in-out; transform-origin: center bottom; } /* うなずくアニメーションを定義 */ @keyframes nodAnimation { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(-20deg); } } .input-text-list { display: block; flex-wrap: column; align-items: flex-start; /* 要素が横に溢れたら改行 */ margin-top: 10px; } .input-text-item { background-color: #f0f0f0; padding: 5px; margin: 5px 0; border-radius: 5px; }
JavaScript
document.addEventListener('DOMContentLoaded', function () { const inputTexts = document.getElementById('inputTexts'); const rubberDuck = document.getElementById('rubberDuck'); const speechBubble = document.getElementById('speechBubble'); const userInput = document.getElementById('userInput'); const inputTextList = []; // ユーザーが入力したときに呼ばれる関数 window.handleUserInput = function () { const inputText = userInput.value.trim(); if (inputText !== '') { nodRubberDuck(); showSpeechBubble(); showInputText(); } }; // ラバーダックがうなずく関数 function nodRubberDuck() { // うなずくアニメーションを適用 rubberDuck.classList.add('nodding'); // アニメーションを解除して通常の姿勢に戻す setTimeout(function () { rubberDuck.classList.remove('nodding'); }, 2000); } // 吹き出しを表示する関数 function showSpeechBubble() { speechBubble.textContent = "クワッ"; speechBubble.style.display = 'block'; // 3秒後に吹き出しを非表示にする setTimeout(function () { speechBubble.style.display = 'none'; }, 3000); } function showInputText() { const inputText = userInput.value.trim(); if (inputText !== '') { inputTextList.push(inputText); const inputTextItem = document.createElement('div'); inputTextItem.className = 'input-text-item'; inputTextItem.textContent = inputText; inputTexts.appendChild(inputTextItem); userInput.value = ''; // 入力欄をクリア inputTexts.scrollTop = inputTexts.scrollHeight; } } });