react-free-scratchcard
Version:
A customizable scratch card component for React with random rewards support
170 lines (165 loc) • 6.41 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
require("./scratchcard.css");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const ScratchCard = _ref => {
let {
scratchImage,
prizeImage,
rewardOptions,
onRewardSelected,
onScratchComplete,
scratchedPoints,
revealButtonText = "Scratch Now",
revealingText = "Revealing...",
scratchRadius = 20,
autoScratchSpeed = 1,
rewardExpiryMinutes = 5,
storeReward = true
} = _ref;
const canvasRef = (0, _react.useRef)(null);
const prizeCanvasRef = (0, _react.useRef)(null);
const animationRef = (0, _react.useRef)(null);
const [isRevealed, setIsRevealed] = (0, _react.useState)(false);
const [isAutoScratching, setIsAutoScratching] = (0, _react.useState)(false);
const [selectedReward, setSelectedReward] = (0, _react.useState)(null);
// Initialize reward selection
(0, _react.useEffect)(() => {
if (rewardOptions && rewardOptions.length > 0) {
const randomIndex = Math.floor(Math.random() * rewardOptions.length);
const reward = rewardOptions[randomIndex];
setSelectedReward(reward);
if (onRewardSelected) {
onRewardSelected(reward);
}
}
}, [rewardOptions, onRewardSelected]);
// Setup canvas
(0, _react.useEffect)(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = canvas.offsetHeight;
// Draw scratch surface
if (scratchImage) {
const img = new Image();
img.src = scratchImage;
img.onload = () => ctx.drawImage(img, 0, 0, width, height);
} else {
ctx.fillStyle = "#ddd";
ctx.fillRect(0, 0, width, height);
}
// Draw prize
const prizeCanvas = prizeCanvasRef.current;
if (!prizeCanvas) return;
const prizeCtx = prizeCanvas.getContext("2d");
prizeCanvas.width = width;
prizeCanvas.height = height;
const imageToUse = selectedReward?.image || prizeImage;
if (!imageToUse) return;
const prizeImg = new Image();
prizeImg.src = imageToUse;
prizeImg.onload = () => prizeCtx.drawImage(prizeImg, 0, 0, width, height);
return () => cancelAnimationFrame(animationRef.current);
}, [scratchImage, prizeImage, selectedReward]);
const autoScratch = () => {
if (isAutoScratching || isRevealed) return;
setIsAutoScratching(true);
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
ctx.globalCompositeOperation = "destination-out";
const width = canvas.width;
const height = canvas.height;
const centerX = width / 2;
const centerY = height / 2;
let angle = 0;
let radius = 10;
const maxRadius = Math.min(width, height) / 2;
const radiusStep = autoScratchSpeed;
const angleStep = 0.2;
const animate = () => {
const x = centerX + radius * Math.cos(angle) + Math.random() * 5;
const y = centerY + radius * Math.sin(angle) + Math.random() * 5;
ctx.beginPath();
ctx.arc(x, y, scratchRadius, 0, Math.PI * 2);
ctx.fill();
angle += angleStep;
radius += radiusStep;
if (radius < maxRadius) {
animationRef.current = requestAnimationFrame(animate);
} else {
cancelAnimationFrame(animationRef.current);
setIsAutoScratching(false);
setIsRevealed(true);
const result = {
reward: selectedReward,
points: selectedReward?.points || scratchedPoints || 0
};
if (onScratchComplete) {
onScratchComplete(result);
}
if (storeReward && result.points > 0) {
const rewardData = {
points: result.points,
expiry: Date.now() + rewardExpiryMinutes * 60 * 1000
};
localStorage.setItem("scratchReward", JSON.stringify(rewardData));
}
}
};
animate();
};
return /*#__PURE__*/_react.default.createElement("div", {
className: "scratch-container"
}, /*#__PURE__*/_react.default.createElement("button", {
className: `scratch-btn ${isRevealed ? "hidden" : ""}`,
onClick: autoScratch,
disabled: isAutoScratching
}, isAutoScratching ? revealingText : revealButtonText), /*#__PURE__*/_react.default.createElement("div", {
className: "card-wrapper",
onClick: autoScratch
}, /*#__PURE__*/_react.default.createElement("canvas", {
ref: canvasRef,
className: "scratch-canvas mt-2"
}), /*#__PURE__*/_react.default.createElement("canvas", {
ref: prizeCanvasRef,
className: `prize-canvas ${isRevealed ? "visible" : ""}`
})));
};
ScratchCard.propTypes = {
// Basic usage
scratchImage: _propTypes.default.string.isRequired,
prizeImage: _propTypes.default.string,
// Random rewards
rewardOptions: _propTypes.default.arrayOf(_propTypes.default.shape({
image: _propTypes.default.string.isRequired,
points: _propTypes.default.number
})),
onRewardSelected: _propTypes.default.func,
// Callbacks
onScratchComplete: _propTypes.default.func,
// Points
scratchedPoints: _propTypes.default.number,
// UI
revealButtonText: _propTypes.default.string,
revealingText: _propTypes.default.string,
// Behavior
scratchRadius: _propTypes.default.number,
autoScratchSpeed: _propTypes.default.number,
// Storage
rewardExpiryMinutes: _propTypes.default.number,
storeReward: _propTypes.default.bool
};
ScratchCard.defaultProps = {
rewardOptions: [],
storeReward: true
};
var _default = exports.default = ScratchCard;