【JavaScript】ドラッグで削るスクラッチカードのクイズの作り方

簡単なクイズやテストを、ユニークなスクラッチカード形式で実装する方法。
CSSとJavaScriptを用いて、「ドラッグで削れるシルバーの領域」を再現します。コピペで簡単に使えます!

スクラッチカードのクイズ実装サンプル

漢字クイズ

「鰈」は何と読む?
答え:かれい

シルバーの領域をドラッグすると、スクラッチが削れるよ!

地理クイズ

富士山は静岡県と山梨県にまたがる

スクラッチ箇所は、2箇所でも3箇所でも設定できます。

ことわざクイズ

の子は
意味:子は親に似る

FontAwesomeのアイコンフォントや、画像を仕込むこともできます。

ソースコード

JavaScriptコード

<script>
(function() {
    function createScratchEffect(scratchElement) {
        const overlay = document.createElement('div');
        overlay.className = 'scratch-overlay';
        scratchElement.appendChild(overlay);

        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        overlay.appendChild(canvas);

        let isDrawing = false;
        let lastX = 0;
        let lastY = 0;

        function initCanvasSize() {
            const rect = scratchElement.getBoundingClientRect();
            canvas.width = rect.width;
            canvas.height = rect.height;

            const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
            gradient.addColorStop(0, '#eeeeee');
            gradient.addColorStop(1, '#bbbbbb');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }

        initCanvasSize();

        function startDrawing(e) {
            isDrawing = true;
            [lastX, lastY] = getPosition(e);
            draw(e);
        }

        function stopDrawing() {
            isDrawing = false;
        }

        function draw(e) {
            if (!isDrawing) return;
            e.preventDefault();

            const [x, y] = getPosition(e);
            ctx.globalCompositeOperation = 'destination-out';
            ctx.beginPath();
            ctx.moveTo(lastX, lastY);
            ctx.lineTo(x, y);
            ctx.lineWidth = 15;
            ctx.lineCap = 'round';
            ctx.stroke();

            lastX = x;
            lastY = y;
        }

        function getPosition(e) {
            const rect = canvas.getBoundingClientRect();
            const clientX = e.clientX || (e.touches && e.touches[0].clientX);
            const clientY = e.clientY || (e.touches && e.touches[0].clientY);
            return [
                clientX - rect.left,
                clientY - rect.top
            ];
        }

        canvas.addEventListener('mousedown', startDrawing);
        document.addEventListener('mousemove', draw);
        document.addEventListener('mouseup', stopDrawing);

        canvas.addEventListener('touchstart', function(e) {
            e.preventDefault();
            startDrawing(e);
        }, { passive: false });

        document.addEventListener('touchmove', function(e) {
            if (isDrawing) {
                e.preventDefault();
                draw(e);
            }
        }, { passive: false });

        document.addEventListener('touchend', stopDrawing);
        document.addEventListener('touchcancel', stopDrawing);
    }

    function initializeScratchEffects() {
        document.querySelectorAll('.scratch').forEach(createScratchEffect);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScratchEffects);
    } else {
        initializeScratchEffects();
    }
})();
</script>

JavaScriptコードは</body>の前に入れます。

49行目のctx.lineWidth = 15;はスクラッチの描画サイズの直径です。要は、シルバーを削るときの指の太さみたいなものです。
30に設定すれば、ひとかきでザックリ削れるようになります。5に設定すれば、チマチマした削り方になります。お好みで調節してね!

HTMLコード

<div class="mondai">「鰈」は何と読む?<br />
答え:<span class="scratch"><span class="scratch-content">かれい</span></span></div>

スクラッチの部分は<span class="scratch"><span class="scratch-content">文字列</span></span>を追加することで増やせます。

クイズのサンプルでは<div class="mondai"></div>でカードを作り、その中でスクラッチを表示しましたが、スクラッチ部分を単独で使うこともできます。

CSSコード

.mondai {
  max-width: 350px; /* カードの最大幅 */
  font-size: 28px; /* テキストのサイズ */
  background: #ff0; /* カードの背景色 */
  color: #000; /* テキストの色 */
  border: solid 3px #000; /* カードの枠線 */
  padding: 15px; /* カード内の余白 */
  border-radius: 15px; /* カードの角の丸み */
  margin: 20px auto; /* カードの外側の余白 */
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); /* カードの影 */
  box-sizing: border-box;
}

.mondai, .scratch {
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}

.scratch {
    display: inline-block;
    background: #fff;/* 削ったスクラッチの背景色 */
    padding: 5px 20px;
    margin: 0 3px;
    border-radius: 30px;
    position: relative;
}

.scratch-content {
    position: relative;
    z-index: 1;
    color: #008;/* 削ったスクラッチの文字色 */
    font-weight: bold;
}

.scratch-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: inherit;
    overflow: hidden;
    z-index: 2;
}

canvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    cursor: pointer;
    background: transparent;
}

9行目のmargin: 20px auto;margin: 20px 0;に変更するとカードの中央揃えが解除されます。

JavaScriptを使わないシンプルなパターン

クリック中だけ答えをチラ見せ

「冬眠鼠」は何と読む?
答え:やまね

シルバーの領域をクリック(またはタップ)している間だけ答えが表示されます。
HTMLとCSSだけで作られているので、実装が簡単です。

ソースコードを見る

HTMLコード

<div class="mondai">
  「冬眠鼠」は何と読む?<br />
  答え:<span class="scratch" tabindex="0"><span class="scratch-content">やまね</span></span>
</div>

CSSコード

.mondai {
  max-width: 350px; /* カードの最大幅 */
  font-size: 28px; /* テキストのサイズ */
  background: #ff0; /* カードの背景色 */
  color: #000; /* テキストの色 */
  border: solid 3px #000; /* カードの枠線 */
  padding: 15px; /* カード内の余白 */
  border-radius: 15px; /* カードの角の丸み */
  margin: 20px auto; /* カードの外側の余白 */
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); /* カードの影 */
  box-sizing: border-box;
}

.scratch {
  display: inline-block;
  background: #fff; /* 削ったスクラッチの背景色 */
  padding: 5px 20px;
  margin: 0 3px;
  border-radius: 30px;
  position: relative;
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;
  -webkit-user-select: none;
  -moz-user-select: none;
}

.scratch-content {
  color: #008; /* 削ったスクラッチの文字色 */
  font-weight: bold;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.scratch::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #eeeeee, #bbbbbb);/* スクラッチのシルバー */
  border-radius: inherit;
  transition: opacity 0.3s ease;
}

.scratch:active::before{
  opacity: 0;
}

.scratch:active .scratch-content{
  opacity: 1;
}

クリックで答えを出す

「樹懶」は何と読む?
答え:なまけもの

シルバーの領域をクリック(またはタップ)すると、シルバー部分が段階的に削れます。
シルバーが削れる過程はCSSアニメーションで実装しており、JavaScript無しで使えます。
簡単かつ「削ってる感」を出したいならこれ!

シルバーを削るアニメーションの再生速度はCSS内で調整できます。

ソースコードを見る

HTMLコード

<div class="mondai">
  「樹懶」は何と読む?<br />
  答え:<span class="scratch" onclick="this.classList.add('is-scratched')"><span class="scratch-content">なまけもの</span>
  </span>
</div>

CSSコード

.mondai {
  max-width: 350px; /* カードの最大幅 */
  font-size: 28px; /* テキストのサイズ */
  background: #ff0; /* カードの背景色 */
  color: #000; /* テキストの色 */
  border: solid 3px #000; /* カードの枠線 */
  padding: 15px; /* カード内の余白 */
  border-radius: 15px; /* カードの角の丸み */
  margin: 20px auto; /* カードの外側の余白 */
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); /* カードの影 */
  box-sizing: border-box;
}

.scratch {
  display: inline-block;
  background: #fff; /* 削ったスクラッチの背景色 */
  padding: 5px 20px;
  margin: 0 3px;
  border-radius: 30px;
  position: relative;
  overflow: hidden;
  vertical-align: middle;
}

.scratch-content {
  color: #008; /* 削ったスクラッチの文字色 */
  font-weight: bold;
  position: relative;
  z-index: 1;
}

.scratch::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(135deg, #eeeeee, #bbbbbb); /* スクラッチのシルバー */
  z-index: 2;
  cursor: pointer;
  transition: clip-path 1s steps(5, jump-none);
}

.scratch:not(.is-scratched)::before {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

.scratch.is-scratched::before {
  clip-path: polygon(100% 0, 100% 0, 100% 100%, 100% 100%);
}

.scratch.is-scratched {
  cursor: default;
}

シルバーが削れる過程のアニメーションは42行目のtransition: clip-path 1s steps(5, jump-none);で設定しています。この設定では1秒間に5段階の削り方、となっています。

transition: clip-path 2s steps(7, jump-none);とすれば、2秒間に7段階の削り方になります。

-JavaScript