Skip to Content
🎲 Welcome to RandBox - Powerful JavaScript Random Data Generation Library! Learn More
📦 Some ExamplesvueScratchCard - Scratch Card

ScratchCard - Scratch Card

ScratchCard is an interactive scratch card component based on Canvas and Vue 3, providing a realistic scratching experience. It supports multiple winning modes including horizontal, vertical, and diagonal lines, as well as custom symbols and winning probability configuration.

📦 Import

<script setup> import { ScratchCard } from '@randbox/vue' // Or import types import type { ScratchCardProps, ScratchCardResult } from '@randbox/vue' </script>

🚀 Basic Usage

<template> <ScratchCard @scratch="handleScratch" /> </template> <script setup> import { ScratchCard } from '@randbox/vue' const handleScratch = (result) => { console.log('Scratch result:', result) if (result.isWinner) { alert(`🎉 Congratulations! You won ${result.winningInfo.prize}`) } } </script>

🎯 Advanced Usage

Custom Grid and Symbols

<template> <ScratchCard :rows="4" :cols="4" :symbols="symbols" :win-probability="0.3" @scratch="handleScratch" /> </template> <script setup> import { ref } from 'vue' const symbols = ref(['🍎', '🍊', '🍋', '🍒', '🍇', '💎', '⭐', '🔔']) const handleScratch = (result) => { console.log('Custom scratch:', result) if (result.isWinner) { const { winningInfo } = result alert(`Win type: ${winningInfo.name} - ${winningInfo.prize}`) } } </script>

Multiple Winning Modes Display

<template> <div class="scratch-game"> <div class="mode-selector"> <label>Select winning mode: </label> <select v-model="winningMode"> <option value="all">All modes</option> <option value="row">Horizontal</option> <option value="col">Vertical</option> <option value="diagonal">Diagonal</option> </select> </div> <ScratchCard :key="cardKey" :rows="3" :cols="3" :symbols="symbols" :win-probability="0.4" @scratch="handleScratch" /> <div v-if="lastResult" class="result-display"> <h4>Latest scratch result:</h4> <div>Winner: {{ lastResult.isWinner ? 'Yes' : 'No' }}</div> <div>Scratch progress: {{ (lastResult.scratchProgress * 100).toFixed(1) }}%</div> <div v-if="lastResult.isWinner" class="win-info"> Win info: {{ lastResult.winningInfo.name }} - {{ lastResult.winningInfo.prize }} </div> </div> </div> </template> <script setup> import { ref, watch } from 'vue' const winningMode = ref('all') const cardKey = ref(0) const lastResult = ref(null) const symbols = ref(['🎯', '🎮', '🎲', '🎪', '🎨', '🎭', '🎺', '🎸']) // Watch mode changes, regenerate card watch(winningMode, () => { cardKey.value++ }) const handleScratch = (result) => { lastResult.value = result if (result.isWinner) { const { type, name, prize } = result.winningInfo console.log(`Win mode: ${type}, Name: ${name}, Prize: ${prize}`) } } </script> <style scoped> .scratch-game { padding: 20px; } .mode-selector { margin-bottom: 20px; } .result-display { margin-top: 20px; padding: 10px; background-color: #f5f5f5; border-radius: 8px; } .win-info { color: green; font-weight: bold; } </style>

Scratch Card with Statistics

<template> <div class="stats-scratch-game"> <!-- Statistics Panel --> <div class="stats-panel"> <h3>Scratch Statistics</h3> <div class="stats-grid"> <div class="stat-item">Total Cards: {{ stats.totalCards }}</div> <div class="stat-item">Wins: {{ stats.wins }}</div> <div class="stat-item">Losses: {{ stats.losses }}</div> <div class="stat-item">Win Rate: {{ winRate }}%</div> <div class="stat-item">Total Prize: ${{ stats.totalPrize }}</div> </div> </div> <div class="current-card"> <h4>Card #{{ currentCard + 1 }}</h4> </div> <ScratchCard :key="currentCard" :rows="3" :cols="3" :symbols="symbols" :win-probability="0.25" @scratch="handleScratch" @new-card="handleNewCard" /> <div class="controls"> <button @click="buyNewCard" class="buy-button"> Buy New Card </button> </div> </div> </template> <script setup> import { ref, computed } from 'vue' const stats = ref({ totalCards: 0, wins: 0, losses: 0, totalPrize: 0 }) const currentCard = ref(0) const symbols = ref(['💰', '💎', '🏆', '🎁', '⭐', '🍀', '🎯', '💯']) const winRate = computed(() => stats.value.totalCards > 0 ? ((stats.value.wins / stats.value.totalCards) * 100).toFixed(1) : '0.0' ) const handleNewCard = () => { currentCard.value++ } const handleScratch = (result) => { stats.value.totalCards++ if (result.isWinner) { stats.value.wins++ stats.value.totalPrize += 100 alert('🎉 Winner! Prize value: $100') } else { stats.value.losses++ } } const buyNewCard = () => { handleNewCard() } </script> <style scoped> .stats-scratch-game { padding: 20px; } .stats-panel { margin-bottom: 20px; padding: 15px; background-color: #e8f4fd; border-radius: 10px; text-align: center; } .stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-top: 10px; } .stat-item { padding: 8px; background: white; border-radius: 4px; font-size: 0.9em; } .current-card { text-align: center; margin-bottom: 20px; } .controls { text-align: center; margin-top: 20px; } .buy-button { padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } .buy-button:hover { background-color: #0056b3; } </style>

Themed Scratch Cards

<template> <div class="themed-scratch"> <div class="theme-selector"> <label>Select theme: </label> <select v-model="currentTheme"> <option v-for="(theme, key) in themes" :key="key" :value="key"> {{ theme.name }} </option> </select> </div> <div :class="['themed-container', `theme-${currentTheme}`]"> <h4>{{ themes[currentTheme].name }} Scratch Card</h4> <ScratchCard :key="themeKey" :rows="3" :cols="3" :symbols="themes[currentTheme].symbols" :win-probability="0.3" @scratch="handleScratch" /> </div> </div> </template> <script setup> import { ref, computed, watch } from 'vue' const currentTheme = ref('fruit') const themeKey = ref(0) const themes = { fruit: { symbols: ['🍎', '🍊', '🍋', '🍒', '🍇', '🥝', '🍓', '🍑'], name: 'Fruit Theme' }, gem: { symbols: ['💎', '💍', '👑', '🏆', '⭐', '✨', '🌟', '💫'], name: 'Gem Theme' }, animal: { symbols: ['🐱', '🐶', '🐸', '🐯', '🦁', '🐻', '🐼', '🐨'], name: 'Animal Theme' } } // Watch theme changes watch(currentTheme, () => { themeKey.value++ }) const handleScratch = (result) => { if (result.isWinner) { alert(`🎉 ${themes[currentTheme.value].name} winner!`) } } </script> <style scoped> .themed-scratch { padding: 20px; } .theme-selector { margin-bottom: 20px; } .themed-container { padding: 20px; border-radius: 15px; border: 2px solid #ddd; text-align: center; } .theme-fruit { background-color: #fff3e0; border-color: #ff9800; } .theme-gem { background-color: #f3e5f5; border-color: #9c27b0; } .theme-animal { background-color: #e8f5e8; border-color: #4caf50; } </style>

Progressive Jackpot Scratch Card

<template> <div class="progressive-scratch"> <!-- Game Info Panel --> <div class="game-info"> <div class="info-item jackpot"> <h4>Progressive Jackpot</h4> <div class="amount">${{ jackpot.toLocaleString() }}</div> </div> <div class="info-item balance"> <h4>Account Balance</h4> <div class="amount">${{ balance }}</div> </div> <div class="info-item cards"> <h4>Cards Purchased</h4> <div class="amount">{{ cardCount }}</div> </div> </div> <div class="card-info"> <div>Card price: ${{ cardPrice }} | Each card adds ${{ cardPrice * 0.5 }} to jackpot</div> <button @click="buyNewCard" :disabled="balance < cardPrice" class="buy-button" :class="{ disabled: balance < cardPrice }" > Buy New Card (${{ cardPrice }}) </button> </div> <ScratchCard v-if="cardCount > 0" :key="cardCount" :rows="3" :cols="3" :symbols="symbols" :win-probability="0.4" @scratch="handleScratch" /> <div v-else class="no-cards"> Buy your first scratch card to start playing! </div> </div> </template> <script setup> import { ref } from 'vue' const jackpot = ref(1000) const cardPrice = ref(10) const balance = ref(100) const cardCount = ref(0) const symbols = ref(['💰', '💵', '💴', '💶', '💷', '🏦', '💳', '📈']) const buyNewCard = () => { if (balance.value < cardPrice.value) { alert('Insufficient balance!') return } balance.value -= cardPrice.value jackpot.value += cardPrice.value * 0.5 // Each card adds half its price to jackpot cardCount.value++ } const handleScratch = (result) => { if (result.isWinner) { const winAmount = Math.random() > 0.95 ? jackpot.value : Math.floor(Math.random() * 100) + 20 if (winAmount === jackpot.value) { alert(`🎰 Super Jackpot! Won progressive jackpot $${jackpot.value}!`) jackpot.value = 1000 // Reset jackpot } else { alert(`🎉 Winner! Won $${winAmount}!`) } balance.value += winAmount } } </script> <style scoped> .progressive-scratch { padding: 20px; } .game-info { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 20px; text-align: center; } .info-item { padding: 15px; border-radius: 10px; } .jackpot { background-color: #fff3cd; } .balance { background-color: #d1ecf1; } .cards { background-color: #d4edda; } .amount { font-size: 24px; font-weight: bold; margin-top: 8px; } .jackpot .amount { color: #d4a853; } .balance .amount { color: #0c5460; } .cards .amount { color: #155724; } .card-info { text-align: center; margin-bottom: 20px; } .buy-button { margin-top: 15px; padding: 12px 24px; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; background-color: #28a745; } .buy-button.disabled { background-color: #6c757d; cursor: not-allowed; } .no-cards { text-align: center; padding: 40px; background-color: #f8f9fa; border-radius: 10px; color: #6c757d; } </style>

📋 API Reference

Props

PropertyTypeDefaultDescription
rowsnumber3Number of grid rows
colsnumber3Number of grid columns
symbolsstring[]['🍎', '🍊', '🍋', '🍒']Available symbols array
winProbabilitynumber0.2Winning probability (between 0-1)
classstring''CSS class name
styleRecord<string, any>{}Inline styles
disabledbooleanfalseWhether disabled

Events

EventParametersDescription
scratch(result: ScratchCardResult)Scratch completion callback
new-card()New card generation callback
game-start()Game start callback
game-end(result: ScratchCardResult)Game end callback

ScratchCardResult

interface ScratchCardResult { grid: string[][] // Card grid data isWinner: boolean // Whether winner scratchProgress?: number // Scratch progress (0-1) winningInfo?: { pattern: string[] // Winning pattern name: string // Winning type name prize: string // Prize description symbol?: string // Winning symbol type?: 'row' | 'col' | 'diagonal' // Winning type positions: Array<{ // Winning positions row: number col: number }> } }

🎨 Style Customization

Container Styles

<style scoped> .scratch-card-container { border: 3px solid #ffc107; border-radius: 15px; box-shadow: 0 8px 25px rgba(255, 193, 7, 0.3); background: linear-gradient(145deg, #fff9c4, #ffecb5); } .scratch-card-container:hover { transform: scale(1.02); transition: transform 0.3s ease; } </style>

Winning Effects

<style scoped> .winning-card { animation: winPulse 2s ease-in-out infinite; border-color: #28a745; box-shadow: 0 0 30px rgba(40, 167, 69, 0.5); } @keyframes winPulse { 0%, 100% { box-shadow: 0 0 30px rgba(40, 167, 69, 0.5); } 50% { box-shadow: 0 0 50px rgba(40, 167, 69, 0.8); } } </style>

🔧 Advanced Features

Custom Winning Rules

<script setup> import { ref } from 'vue' const symbols = ref(['A', 'B', 'C', 'D', 'E']) // Custom winning detection logic const checkCustomWinning = (grid) => { // Check special patterns const center = grid[1][1] const corners = [grid[0][0], grid[0][2], grid[2][0], grid[2][2]] // Rule 1: Center + four corners same if (corners.every(symbol => symbol === center)) { return { isWinner: true, type: 'center_corners', name: 'Center Corners', prize: 'Special Reward' } } // Rule 2: X pattern if (grid[0][0] === grid[1][1] && grid[1][1] === grid[2][2] && grid[0][2] === grid[1][1] && grid[1][1] === grid[2][0]) { return { isWinner: true, type: 'x_pattern', name: 'X Pattern', prize: 'Grand Prize' } } return { isWinner: false } } const handleScratch = (result) => { const customResult = checkCustomWinning(result.grid) if (customResult.isWinner) { alert(`Special win: ${customResult.name} - ${customResult.prize}`) } } </script>

Animation Enhancement

<template> <div class="animated-scratch" :class="{ scratching: isScratching }"> <ScratchCard @game-start="handleGameStart" @game-end="handleGameEnd" /> <Transition name="win-celebration"> <div v-if="lastWin" class="win-overlay"> 🎉 {{ lastWin.name }} 🎉 <br /> {{ lastWin.prize }} </div> </Transition> </div> </template> <script setup> import { ref } from 'vue' const isScratching = ref(false) const lastWin = ref(null) const handleGameStart = () => { isScratching.value = true } const handleGameEnd = (result) => { isScratching.value = false if (result.isWinner) { lastWin.value = result.winningInfo // Delay hiding win animation setTimeout(() => { lastWin.value = null }, 3000) } } </script> <style scoped> .animated-scratch { position: relative; } .scratching { animation: scratchShake 0.2s ease-in-out infinite; } @keyframes scratchShake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-1px); } 75% { transform: translateX(1px); } } .win-overlay { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(40, 167, 69, 0.9); color: white; padding: 20px; border-radius: 10px; text-align: center; z-index: 10; } .win-celebration-enter-active { animation: bounceIn 0.5s; } .win-celebration-leave-active { animation: fadeOut 0.3s; } @keyframes bounceIn { 0% { transform: translate(-50%, -50%) scale(0); } 50% { transform: translate(-50%, -50%) scale(1.1); } 100% { transform: translate(-50%, -50%) scale(1); } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } </style>

🎯 Best Practices

1. Winning Probability Design

<script setup> // Reasonable winning probability configuration const probabilities = { easy: 0.4, // Easy mode, 40% win rate normal: 0.25, // Normal mode, 25% win rate hard: 0.15, // Hard mode, 15% win rate extreme: 0.05 // Extreme mode, 5% win rate } </script>

2. Symbol Design Recommendations

<script setup> // Recommended symbol combinations const symbolSets = { // High contrast, easily distinguishable classic: ['🔴', '🟢', '🔵', '🟡', '🟣', '🟠', '⚫', '⚪'], // Themed symbols casino: ['🎰', '🃏', '🎲', '💰', '💎', '🏆', '⭐', '🍀'], // Avoid using similar symbols bad: ['😀', '😃', '😄', '😁'] // Too similar, hard for users to distinguish } </script>

3. Error Handling

<template> <div> <div v-if="error" class="error-message"> Error: {{ error }} </div> <ScratchCard :rows="3" :cols="3" :win-probability="0.3" @scratch="handleScratch" @error="handleError" /> </div> </template> <script setup> import { ref } from 'vue' const error = ref('') const handleScratch = (result) => { error.value = '' console.log('Scratch success:', result) } const handleError = (err) => { error.value = err.message console.error('Scratch card error:', err) } </script> <style scoped> .error-message { color: red; margin-bottom: 10px; padding: 10px; background-color: #ffe6e6; border-radius: 4px; } </style>

🐛 Common Issues

Q: How to achieve true randomness?

A: The component uses RandBox’s Mersenne Twister algorithm, providing high-quality random number generation to ensure each scratch is truly random.

Q: Can scratch gestures be customized?

A: Currently the component supports mouse and touch scratching, scratch area and sensitivity can be adjusted through Canvas events.

Q: How to control winning probability?

A: Use the winProbability property, range is 0-1, for example 0.3 represents 30% winning probability.

Q: What winning modes are supported?

A: Supports horizontal, vertical, and diagonal three basic modes, and more complex winning rules can be implemented through custom logic.

Q: How to integrate with Vue Router?

A: Can be used normally in route components, or dynamically configure symbols and probability through route parameters.

Last updated on: