react-scratch-ticket
Version:
This is a scratch ticket component, basic on React
238 lines (183 loc) β’ 7.95 kB
Markdown
<h1 align="center">Welcome to react-scratch-ticket π</h1>
<p>
<img src="https://img.shields.io/badge/npm-%3E%3D10.0.0-blue.svg" />
<img src="https://img.shields.io/badge/node-%3E%3D20.0.0-blue.svg" />
<a href="https://github.com/RexHung0302/react-scratch-ticket#readme" target="_blank">
<img alt="Documentation" src="https://img.shields.io/badge/documentation-yes-brightgreen.svg" />
</a>
<a href="https://github.com/RexHung0302/react-scratch-ticket/graphs/commit-activity" target="_blank">
<img alt="Maintenance" src="https://img.shields.io/badge/Maintained%3F-yes-green.svg" />
</a>
<a href="https://github.com/RexHung0302/react-scratch-ticket/blob/master/LICENSE" target="_blank">
<img alt="License: MIT" src="https://img.shields.io/github/license/RexHung0302/react-scratch-ticket" />
</a>
</p>
[En](README.md) | [ηΉδΈ](README-tw.md) | [η°‘δΈ](README-cn.md)
> This is a scratch ticket component, basic on React
## π¨π¨ Readme First Please π¨π¨
This package is inspired by **[react-scratchcard-v2](https://github.com/dopey2/react-scratchcard-v2)** and **[scratch-card](https://github.com/1587315093/scratch-card)**. Upon initial installation, I found that the former did not work on mobile devices, and the latter lacked the feature to reveal the entire scratch ticket with one click. After some consideration, I decided to create this scratch ticket component using canvas and React.
To address mobile usage, I implemented `canvas.addEventListener('touchstart', startScratching)`, `canvas.addEventListener('touchend', startScratching)` and `canvas.addEventListener('touchmove', touchMoveHandler)` for touch events. For the "Clear card" feature, I used `ctx.clearRect(0, 0, width, height)` to reveal the canvas layer.
If you have any ideas or features you'd like to see added to this package, feel free to leave them in the **[issues](https://github.com/RexHung0302/react-scratch-ticket/issues)** section. The author will continue to update with more ideas. A **[todo-list](#todo-list)** will be available below. Lastly, I hope this package enhances your experience, and I wish you a wonderful day. Thank you!
### π [Homepage](https://github.com/RexHung0302/react-scratch-ticket#readme)
### π¦ [NPM](https://www.npmjs.com/package/react-scratch-ticket)
### π [Blog](https://rexhung0302.github.io/2025/03/09/20250309/)
## Preview

> With Background Color Demo

> With Background Image Demo

> Reset and Clear function
## Prerequisites
- npm >=10.0.0
- node >=20.0.0
## Install
```sh
npm i react-scratch-ticket
```
```sh
yarn i react-scratch-ticket
```
```sh
pnpm i react-scratch-ticket
```
## Usage
### Use in your Component
```tsx
import React from "react";
import ScratchTicket from "react-scratch-ticket";
import useIndexController from "./useIndexController.ts";
import './style.scss';
const App = () => {
const { prizeInfo, scratchTicketRef, completeHandler, initDoneHandler, resetDoneHandler, clickResetBtnHandler, clickClearCardBtnHandler } = useIndexController();
return (
<div className="container">
<h2 className="font-bold">React Scratch Ticket Demo</h2>
<p className="container__title">100X</p>
<div className="content">
<ScratchTicket
ref={scratchTicketRef}
containerClassName="rounded-[10px] my-4"
brushSize={10}
width={309}
height={52}
childrenCenter
maskingLayerImg='https://picsum.photos/309/52'
maskingLayerColor="yellow"
finishPercent={70}
onComplete={completeHandler}
onInitDone={initDoneHandler}
onResetDone={resetDoneHandler}
>
{prizeInfo.name}
</ScratchTicket>
</div>
<div className="flex gap-4 mt-4">
<button onClick={clickResetBtnHandler} className="button">Reset</button>
<button onClick={clickClearCardBtnHandler} className="button">Clear Card</button>
</div>
</div>
);
};
```
### useIndexController.tsx
```ts
import { useEffect, useRef, useState } from "react";
import { ScratchTicketImperative } from "react-scratch-ticket";
/**
* @description Fake Prize Array
*/
const fakePrizeArray = [
{ name: "You will have a nice day" },
{ name: "Keep up the good work" },
{ name: "You are awesome, win $9999 cash" },
{ name: "You are the best, win a car" },
{ name: "$1000 cash" },
{ name: "$5000 cash" },
];
const useIndexController = () => {
const scratchTicketRef = useRef<ScratchTicketImperative>(null);
const [prizeInfo, setPrizeInfo] = useState({ name: "Keep up the good work" });
/**
* @description init done handler
*/
const initDoneHandler = () => console.log("init done");
/**
* @description complete handler
*/
const completeHandler = () => console.log("complete");
/**
* @description reset done handler
*/
const resetDoneHandler = () => console.log("reset done");
/**
* @description click reset button handler
*/
const clickResetBtnHandler = () => {
scratchTicketRef.current?.reset()
};
/**
* @description click clean card button handler
*/
const clickClearCardBtnHandler = () => scratchTicketRef.current?.clearCard();
/**
* @description Init Random Prize
*/
useEffect(() => {
const randomIndex = Math.floor(Math.random() * fakePrizeArray.length);
setPrizeInfo(fakePrizeArray[randomIndex]);
}, []);
return {
prizeInfo,
scratchTicketRef,
completeHandler,
initDoneHandler,
resetDoneHandler,
clickResetBtnHandler,
clickClearCardBtnHandler,
}
};
export default useIndexController;
```
## Type
### Props
| Name | Type | Required | Default |
| ---- | ---- | ---- | ---- |
| width | `number` | β
| |
| height | `number` | β
| |
| children | `React.ReactNode` | β
| |
| brushType | `'circle' \| 'square'` | | `'circle'` |
| brushSize | `number` | | `20` |
| finishPercent | `number` | | `50` |
| maskingLayerImg | `string` | | |
| maskingLayerColor | `string` | | `#ddd` |
| animationDuration | `number` | | `50` |
| containerClassName | `string` | | |
| childrenCenter | `boolean` | | `false` |
| onComplete | `() => void` | | `() => {}` |
| onInitDone | `() => void` | | `() => {}` |
| onResetDone | `() => void` | | `() => {}` |
### Ref
| Name | Type | Description |
| ---- | ---- | ---- |
| reset | `() => void` | Resets the scratch ticket to its initial state, allowing it to be scratched again. |
| clearCard | `() => void` | Immediately clears the scratch ticket, revealing the prize underneath. |
## TODO List
- [ ] Multiple scratchable areas on the same scratch ticket
- [ ] Clear animation optimization (e.g., automatic scratch animation from left to right, instead of disappearing all at once)
- [ ] Add a restricted area for scratch completion on the scratch ticket
- [x] Release the first version
## Author
π€ **RexHung0302**
* Website: https://rexhung0302.github.io/
* Github: [@RexHung0302](https://github.com/RexHung0302)
* LinkedIn: [@https:\/\/www.linkedin.com\/in\/chiatse-hung-908b72204\/](https://linkedin.com/in/https:\/\/www.linkedin.com\/in\/chiatse-hung-908b72204\/)
## π€ Contributing
Contributions, issues and feature requests are welcome!<br />Feel free to check [issues page](https://github.com/RexHung0302/react-scratch-ticket/issues). You can also take a look at the [contributing guide]( ).
## Show your support
Give a βοΈ if this project helped you!
## π License
Copyright Β© 2025 [RexHung0302](https://github.com/RexHung0302).<br />
This project is [MIT](https://github.com/RexHung0302/react-scratch-ticket/blob/master/LICENSE) licensed.
***
_This README was generated with β€οΈ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_