UX Collective

We believe designers are thinkers as much as they are makers. https://linktr.ee/uxc

Follow publication

Developing a scratch card track reveal for Pop Smoke

Lee Martin
UX Collective
Published in
8 min readJul 25, 2021

A stack of Pop Smoke Woocards

Scratch Card

The desktop UX experience

Scratch Cover

<template>
<div class="scratch-cover">
<canvas ref="canvas"></canvas>
</div>
</template>

Covering

let canvas = this.$refs.canvas// set the canvas to match it's parent size
canvas.height = canvas.parentElement.getBoundingClientRect().height
canvas.width = canvas.parentElement.getBoundingClientRect().width
let context = canvas.getContext('2d')// draw the associated section of card image
context.drawImage(
cardImage,
24,
[260, 336, 412, 488][cardIndex],
432,
64,
0,
0,
canvas.width,
canvas.height
)

Scratching

let hammer = new Hammer(canvas)// set pan
hammer.get('pan').set({
direction: Hammer.DIRECTION_ALL
})
hammer.on('panmove', e => {
// set the composite operation
context.globalCompositeOperation = 'destination-out'
// "draw" the eraser image
context.drawImage(
eraserImage,
e.center.x - e.target.getBoundingClientRect().left - 25,
e.center.y - e.target.getBoundingClientRect().top - 25
)
// update scratched progress
this.updateProgress()
// generate fleck
this.$nuxt.$emit('fleck')
})

Scratch Progress

let counter = 0// get the canvas image data
let imageData = context.getImageData(
0,
0,
canvas.width,
canvas.height
)
// get the total length of image data
let imageDataLength = imageData.data.length
// loop through all pixels and if r, g, b, & a is zero, increment
for (let i = 0; i < imageDataLength; i += 4) {
if (imageData.data[i] === 0 && imageData.data[i+1] === 0 && imageData.data[i+2] === 0 && imageData.data[i+3] === 0) {
counter++
}
}
// progress is counter divided by overall area
let progress = counter >= 1 ? counter / (canvas.width * canvas.height) : 0
// emit the new progress
this.$emit('updateProgress', Math.round(progress * 100) / 100)

Scratch Content

Early prototype of the scratch card
this.cards.push({
scratched: false,
areas: Array(4).fill().map((item, i) => {
let content
if (Math.random() < 0.1) {
content = {
type: 'track',
data: tracks[Math.floor(Math.random() * tracks.length)]
}
} else {
content = {
type: 'empty'
}
}
return {
content: content
}
}
})

Scratch Area

<template>
<div id="scratch-area">
<ScratchContent :content="area.content" />
<ScratchCover @updateProgress="progress = $event" />
<div>
</template>
computed: {
scratched() {
return this.progress > 0.1
}
}
watch: {
scratched(newVal, oldVal) {
this.$emit('updateScratched', newVal)
}
}
computed: {
scratched() {
return this.areas.every(area => area.scratched)
}
}

Scratch Effect

The scratch UX
generateFlecks(e) {
for (let i = 0; i < 3; i++) {
particles.push(
getRandomArbitrary(e.center.x - 10, e.center.x + 10),
getRandomArbitrary(e.center.y - 10, e.center.y + 10),
e.velocityX,
e.velocityY,
['#909092','#000000','#FFFFFF'][Math.floor(Math.random() * 3)]
)
}
}
getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min
}
render() {
requestAnimationFrame(this.render)
let canvas = this.$refs.canvas let context = canvas.getContext('2d') // clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height)
// render particles
particles.forEach(particle => {
particle[0] += particle[2] // x
particle[1] += particle[3] // y
particle[3] += 0.1 // velocity
if (particle[1] > window.innerHeight) {
// remove particle
this.particles.splice(i, 1)
} else {
// draw particle
context.fillStyle = particle[4]
context.fillRect(particle[0], particle[1], 1, 1)
}
})
}

Thanks

The cover for Faith by Pop Smoke
The UX Collective donates US$1 for each article we publish. This story contributed to World-Class Designer School: a college-level, tuition-free design school focused on preparing young and talented African designers for the local and international digital product market. Build the design community you believe in.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Written by Lee Martin

Netmaker. Playing the Internet in your favorite band for two decades. Previously Silva Artist Management, SoundCloud, and Songkick.

No responses yet

Write a response