3

Developing a scratch card track reveal for Pop Smoke

 2 years ago
source link: https://uxdesign.cc/developing-a-scratch-card-track-reveal-for-pop-smoke-e44691a87acf
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Developing a scratch card track reveal for Pop Smoke

A stack of Pop Smoke Woocards

Last year I had the opportunity to help market Pop Smoke’s debut posthumous record, Shoot for the Stars, Aim for the Moon, soon after his tragic passing. Though that track reveal concept was developed in a rush, I’m constantly receiving kind words and enthusiasm about it. Thanks to Pop Smoke for giving it the best possible vehicle.

Flash forward to earlier this month… Steven Victor, Republic Records, and the artists’ estate have Pop Smoke’s second posthumous record, Faith, prepared to release on his birthday, July 20th. Once again, we were interested in introducing the tracklist in a compelling way. The tracklist reveal has always been a moment of fan speculation and interest, and that is especially true in hip hop. How many songs? Who’s featured? Do the titles hint at the subject matter? I was excited to think up a new concept.

Well, as it turns out, I didn’t have to. After a quick call with the excellent Republic Records marketing team, discussing Pop’s connection to the grit of New York City, we landed on a mechanism: scratch cards. What if fans were asked to scratch their way to the titles and work together to assemble the final arrangement of songs? We could also integrate some contesting (though we didn’t do this in the end.)

The outcome was a positively received and wildly valuable activation for Pop Smoke. Check it out today and read on to learn how it came together. Also, don’t forget to stream Faith.

Scratch Card

The desktop UX experience

The first thing I tackled was the layout of the scratch card. While I could be inspired by the design of real-world scratch cards, the layout of our digital scratch card had to be based on responsive design principles. That’s because it was going to be interacted with on screens of all sizes. It also helped to know the content which users would be scratching to reveal: track titles. Track titles come in various lengths so a longer horizontal area would work best. A good example of this is the labels you might find in a jukebox. I figured I could fit four horizontal scratch areas and leave room for a scratch card header and footer.

With a layout in mind, I started to consider the functionality. Users should be able to scratch each of the four scratchable areas to reveal a piece of content. Usually that content would simply be a “Try again Woo” message but there was a small chance it would be one of the track titles. At any point, we should know how much of the scratchable area was scratched and in turn be able to decide if a card was indeed scratched. At this point, the user should be able to receive a new card. I knew

was going to be the perfect solution for this and broke it down into several components.
  • <ScratchContent> — the content to be revealed
  • <ScratchCover> — the scratchable surface which conceals content
  • <ScratchArea> — layout of scratch-content and scratch-cover
  • <ScratchCard> — the card, holds four scratch-areas

Using props, methods, and listeners, each component should be able to inform the other components of the card.

Scratch Cover

The core experience of this campaign is allowing the user to actually do some scratching so the <ScratchCover> Vue component is probably the most important. This was achieved using HTML <canvas> and the touch gestures library Hammer.js. The component has a single element: the <canvas> tag.

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

Covering

The first thing we’ll do is draw the cover image onto the <canvas> directly from the full card image using the drawImage method. There is a subtle difference between each scratch area cover due to the smoke in the background so this is an organized way of handling things.

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().widthlet 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
)

All of the sizes and positioning used in the drawImage method were just manually pulled from the original card design I put together in

.

Scratching

The actual scratching interaction is powered by Hammer.js. As users pan over the <canvas> with their mouse or finger, they are actually erasing that area. In order to keep the erasing interesting, we’ll use a pre-generated eraser image. To erase with the image, we just need to use the ‘destination-out’ globalCompositeOperation. Which is described as, “The existing content is kept where it doesn’t overlap the new shape.” Here’s how it all comes together.

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')})

This is also when we’ll handle calculating how much of the area has been scratched (which we’ll discuss next) and generate animated flecks (which we’ll discuss later.)

Scratch Progress

As the user scratches the cover, we’ll calculate how much they have scratched (between 0.0 and 1.0) and inform other components of the card. We can do this by checking which percentage of the <canvas> consists of transparent pixels and then emit the new progress. In order to get all the pixels, we’ll use the getImageData() method. This will return the red, green, blue, and alpha amounts of each pixel as one long array. Step by 4 in the for loop in order to check each pixel. Then simply divide the counted transparent pixels by the total pixel area of the <canvas> to get the 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

1*6qrkT6F6_Wer9RyUVj5A6A.jpeg?q=20
developing-a-scratch-card-track-reveal-for-pop-smoke-e44691a87acf
Early prototype of the scratch card

There isn’t anything all that spectacular about the <ScratchContent> component. It is either going to be the “Try Again Woo” empty image or possibly one of the track images. This is decided back when the <ScratchCard> component is originally initialized. First, I’ve integrated all of the tracks into the experience using the @nuxt/content module. Then, I have a generateCard() method which establishes what each of the 4 scratch-areas will conceal. There is a 1 in 10 chance the area will conceal one of the tracks, which are selected at random. If not, we’ll simply display the empty image.

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
}
}
})

An earlier version of this app had contesting built in. I added that to our experience here by adding a 3rd possible content type, contest, with even lower odds. We ended up dropping that piece but it could be an awesome tactic for a future campaign using scratch cards. If the user scratched off a contest block, their email address was automatically entered to win.

Scratch Area

In addition to laying out the scratch-cover and scratch-content components on top of each other, the <ScratchArea> component listens for the change in progress from the scratch-cover component and decides whether or not an area is scratched if the scratch progress exceeds a specified threshold. In the case of our app, 10%. Here’s what that looks like.

<template>
<div id="scratch-area">
<ScratchContent :content="area.content" />
<ScratchCover @updateProgress="progress = $event" />
<div>
</template>

As the local progress variable changes, we can use a computed property to decide if scratched.

computed: {
scratched() {
return this.progress > 0.1
}
}

And we’ll watch for changes to scratched so when it does flip to true, we can inform the <ScratchCard> parent component.

watch: {
scratched(newVal, oldVal) {
this.$emit('updateScratched', newVal)
}
}

Back on the <ScratchCard> component, we’ll have a similar computed method which checks to see if all areas are scratched, in turn determining if the card itself was scratched.

computed: {
scratched() {
return this.areas.every(area => area.scratched)
}
}

Isn’t

great?

Scratch Effect

The scratch UX

If you’ve ever actually scratched a scratch card, you’ll know how messy they are as the cover material flecks away. This experience would not have been complete without a representation of this mess.

In order to simulate this, I’ve placed a full bleed effects<canvas> over the entire experience with CSS pointer-events set to none. Back on the <ScratchCover>, you’ll recall I’m emitting a fleck action and passing the Hammer.js pan event. When this fleck event is called, we’ll generate a burst of particles at the pan position and animate them falling down the screen. We can even bring in the velocity of the pan to give the animation more visual interest. First, let’s generate some fleck particles. Each particle has a x position, y position, x velocity, y velocity, and color.

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)]
)
}
}

Note: I’m using a getRandomArbitrary method to generate a random number between a minimum and maximum number.

getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min
}

We can then animate these particles using the requestAnimationFrame method so they appear to fall down the screen. This is done by looping through each particle and adjusting the x and y position using their associated velocity values. In addition, the y velocity is increased to make it feel a bit more like gravity is pulling it down. When a particle falls out of view, we can remove it from the particles array so it is no longer animated.

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) }
})
}

We actually added another delighter using a similar particle animation engine which emits a burst of blue jewels when a track is revealed. In that case, the jewels y position begins above the web app and the jewels fall down like rain.

Thanks

The cover for Faith by Pop Smoke

Thanks again to Steven Victor and the Pop Smoke estate for allowing me to help promote Pop’s music once again. Special thanks to my collaborators at Republic Records who help make projects like this happen: Tim Hrycyshyn, Allegra Chautin, and Elliot Althoff. Let’s do it again soon. In the meantime, if you’re reading this, head over and stream Faith by Pop Smoke now. 💫

0*4c-bE0_16U2-nyuX.png?q=20
developing-a-scratch-card-track-reveal-for-pop-smoke-e44691a87acf
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.

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK