vue + fabricjs implement scratch game

Cai Li Creative Issue 1 | Scratch Lottery·Ten Times Good Luck

preview.png

Everyone has played scratch-off games, and Cai Li is also deeply affected by it, but she just can’t help it; especially Ten times better luck, which always makes people feel happy It feels like there is a high chance of winning; today Cai Li made an online Scratch-Off – Ten Times Good Luck for everyone’s entertainment.

Foreword

After Customized Avatar 2.0 was launched, I thought about doing a column about creativity;
image.png

I also like to work on a creative, interesting, and whimsical project; in my short life as a programmer, I always have to leave something behind!

image.png

Column

Please allow me to introduce my column – Cai Li Creative

As the name suggests, this column collects creative, interesting and fun projects that I have made Most of them are front-end. Because it is a creative column, it is updated from time to time, maybe once every two days, maybe once every half month. Inspiration is always so fleeting, and I want to preserve the only, ethereal inspiration I have.

In order to keep updating, I have opened 100 creative projects on github. You can star them. Interested friends can subscribe to the column. , follow github.

Preview effect

It’s still the old rule, go to the link first~

Scratch-off experience address (main site)

Scratch-off experience address (github pages)

github project address (welcome?)

If you like it, move your little hand and click star? Oh, thank you!

Project structure

html | less | fabricjs | vue // html direct reference

Required materials

image.png

Ideas

  1. Use HTML to restore an unscratched scratch card and use it to generate a mask image.
  2. Then use html to make a scratched card.
  3. Draw the mask image on the canvas and use the eraser brush to implement the scratch function.

Development

Logical sorting

I sorted out the following points

1. Initialize winning numbers
awardNum : this.createRandomNum()

/* Get a random number within 60, and fill in zeros if there are less than 2 digits */
createRandomNum () {<!-- -->
    const randomNum = Math.floor(Math.random() * 59) + 1
    return randomNum > 9 ? randomNum : '0' + randomNum
}
2. Initialize scratch numbers

There are a total of 10 lines in Lucky Ten Times. From bottom to top, starting from 1 scratch number, rows 8, 9, and 10 are 8 scratch numbers respectively.

We first disrupt the default bonus, and then randomly initialize the scratch numbers

defaultNumList: [[500], [60], [60], [20], [200], ['400,000'], [10], [100], [40], [500] ],
numList: [],

initNumList () {<!-- -->
    this.numList = this.defaultNumList.sort(() => Math.random() - 0.5).map((row, rowIndex) => {<!-- -->
        const rowTotal = rowIndex > 6 ? 8 : rowIndex + 1

        for (let i = 0;i < rowTotal; i + + ) {<!-- -->
        row.unshift(this.createRandomNum())
        }

        return row
    }).reverse()
},
3. Draw canvas and initialize brush

Here we introduce fabricjs’s EraserBrush. However, fabricjs does not build it by default and we need to add it in the build item.

Document address

Build address

image.png
Check this box to package

initMask () {<!-- -->
    this.canvas = new fabric.Canvas('mask', {<!-- --> isDrawingMode: true });

    /* Set brush */
    this.canvas.freeDrawingBrush = new fabric.EraserBrush(this.canvas)
    this.canvas.freeDrawingBrush.width = this.defaultSize

    /* Draw mask card */
    const backgroundImageUrl = './img/mask.png'; // Replace with your underlying image
    fabric.Image.fromURL(backgroundImageUrl, (img) => {<!-- -->
        img.set({<!-- --> selectable: false });
        img.scaleToWidth(this.canvas.width, true)
        this.canvas.add(img);
        this.loading = false
    });
}

At this point, the basic logic of the scratch-off lottery has been implemented. The following is the implementation of page

Page

Implementing mask images

First make a card box with a width and height of 346 * 450, which contains the head, scratch area and interpretation area.

<div class="card">
    <!-- Card header -->
    <div class="card-head">
        <img src="./img/ten.png" alt="">
        <img src="./img/40.png" alt="">
    </div>
    <!-- Scratch area -->
    <div class="card-main"></div>
    <!-- Gameplay explanation -->
    <div v-if="false" class="card-desc">
        If any "my number" is the same as the "winning number", you can get the corresponding bonus;
        If you scratch out the number "10", you will get 10 times the corresponding bonus for that row.
    </div>
</div>

Add a background to your card

background: #f4f4f4 url("../img/bg.png") center no-repeat;
Scratch area

The scratch area is traversed by the two-dimensional array defaultNumList. There are 10 rows in total, and the last element of each row is the bonus.

<div class="card-main">
    <div v-for="(row, rowIndex) in numList" :key="rowIndex" class="card-main-row">
        <div v-for="(num, numIndex) in row" :key="numIndex" class="card-main-col">
            <template v-if="numIndex === row.length - 1">
                <div class="gold">
                    <span>Bonus{<!-- -->{ cnNumList[9 - rowIndex] }}</span>
                    <img src="./img/gold.png" alt="">
                </div>
            </template>
            <template v-else>
                <img class="money" src="./img/money.png" alt="">
            </template>
        </div>
    </div>
</div>
Positioned element
<!-- Positioning element -->
<!-- Cornucopia background -->
<img class="card-pot" src="./img/pot.png" alt=""></img>
  
<!-- Dotted stars background -->
<img class="card-star" src="./img/star.png" alt=""></img>
  
<!-- Winning number area -->
<div class="card-award">
    <p>prize number</p>
    <img src="./img/money.png">
</div>
  
<!-- 10 chances -->
<div class="card-ten">
    <p>10 times</p>
    <p>Chances of winning</p>
</div>
Canvas area
<!-- canvas -->
<canvas id="mask" width="346" height="450"></canvas>

See the source code for the css code, it’s not complicated.

Ok, this way we draw the unscratched effect, then use the browser to take a screenshot, and the mask image is saved.

mask.png

Realize the scraped page

In order to make the scratching effect more realistic, the general layout cannot be changed, and some elements can only be replaced accordingly.

Header area

The picture in the header area is changed to the text “Scratch Results”

<!-- Card header -->
<div class="card-head">
    <div class="card-head-result">Scratch prize results</div>
</div>
Scratch area

Change coin images to scratch numbers

<div class="num">{<!-- -->{ num }}</div>

Change the text “Bonus One” in Jin Yuanbao to the specific winning number

<div class="gold">
    <span>{<!-- -->{ num }} yuan</span>
    <img src="./img/gold.png" alt="">
</div>
Winning area

Change the coins in the winning area to numbers

<!-- Winning number area -->
    <div class="card-award">
    <p>prize number</p>
    <div>{<!-- -->{ awardNum }}</div>
</div>
Hide 10 winning areas
<!-- 10 chances -->
<div v-if="false" class="card-ten">
    <p>10 times</p>
    <p>Chances of winning</p>
</div>
Cancel card background and others

In this way, we will get the effect after scratching the prize
After scratching.png

Optimization

1. Optimize the scratch-off brush

There will be a thousand Hamlets in the eyes of a thousand readers. Some people want to scratch faster, and some people want to take it slow. That’s me; I have added a brush adjustment function for everyone to choose from. .

<input v-model="defaultSize" type="range" min="5" max="40" @change="sizeChange">

The brush default is 12, the minimum is 5, and the maximum is 40

defaultSize: 12

sizeChange () {<!-- -->
    this.canvas.freeDrawingBrush.width = this.defaultSize
}

2. Optimize loading

It takes time to draw the mask during initialization, which will cause flickering and award exposure. I made a loading animation here.

<!--Loading-->
<div v-if="loading" class="loading">
    <svg viewBox="25 25 50 50" class="circular">
        <circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
    </svg>
</div>
  
<div v-show="!loading" class="card-wrap"></div>

The loading effect here uses elemnet ui

/* initialized to true */
loading: true

/* After drawing the mask image, the loading animation ends */
initMask () {<!-- -->
    this.canvas = new fabric.Canvas('mask', {<!-- --> isDrawingMode: true });

    /* Draw mask card */
    const backgroundImageUrl = './img/mask.png'; // Replace with your underlying image
    fabric.Image.fromURL(backgroundImageUrl, (img) => {<!-- -->
        this.loading = false
    });
}

Open source

100 creative projects officially launch today, and everyone is invited to come and witness it.

When manpower is finally exhausted, like-minded friends welcome me and let’s encourage each other!

The lingering sound

We don’t have a clue about the next creative project yet. As the saying goes, if there is one, there are two. The first creative project is already here, but the second one is still far away!

Looking forward to the next little idea, see you soon~