JS - FortuneWheel - javascript

I want to develop a kind of wheel of fortune with javascript. I want to define both the output and the start. If the user turns the wheel with a minimum speed, the wheel should rotate a few times depending on the rotation speed, before it stops on the desired field.
So far I have only been able to implement the basics, but I hope that somebody can help me to implement the complete process, since I can't get any further now.
HOW TO MAKE WHEEL ROTATING A SERVERAL TIMES ENDING WITH THE targetAngle ?
let element = document.getElementById("wheel");
let region = new ZingTouch.Region(element);
let currentAngle = 0, // this is the current angle of the wheel by the rotate gesture
targetAngle = 0; // blue field is the target where the wheel should stop
let minimumAngle = 30, // minimumAngle to make the wheel spinning
rotatedAngle = 0, // total spinned angle of the wheel by the rotate gesture
spinning = false
setInterval(() => {
rotatedAngle = 0;
}, 200)
region.bind(element, 'rotate', function(e) {
if (rotatedAngle > minimumAngle && rotatedAngle > 180) {
spinning = true;
document.getElementById("spinning").innerHTML = "spinning = true"
// HOW TO MAKE WHEEL ROTATING A SERVERAL TIMES ENDING WITH THE targetAngle ?
} else if (!spinning) {
currentAngle += e.detail.distanceFromLast;
rotatedAngle += e.detail.distanceFromLast;
element.style.transform = 'rotate(' + currentAngle + 'deg)';
}
})
#wheel {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 20%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/zingtouch/1.0.6/zingtouch.min.js"></script>
<img id="wheel" src="https://reel-mkt.com/templates/fortune_reel_lp/img/wheel.png">
<p id="spinning">spinning = false</p>

This is how I would do it:
I need an event like "click" to begin spinning
I'm calculating the targetAngle as a random angle: targetAngle = currentAngle + Math.random() * 360 + minimumAngle;
I'm using requestAnimationFrame for the animation.
I'm calculating the speed as 1/10 of the distance between the targetAngle and the currentAngle
I'm not using ZingTouch as you intended; I don't use the rotatedAngle either.
I hope this is what you need.
let element = document.getElementById("wheel");
/*let region = new ZingTouch.Region(element);*/
let currentAngle = 0, // this is the current angle of the wheel by the rotate gesture
targetAngle = 0; // blue field is the target where the wheel should stop
let minimumAngle = 30, // minimumAngle to make the wheel spinning
//rotatedAngle = 0, // total spinned angle of the wheel by the rotate gesture
spinning = false;
let rid = null;
element.addEventListener("click", () => {
targetAngle = currentAngle + Math.random() * 360 + minimumAngle;
if (spinning) {
spinning = false;
if (rid) {
cancelAnimationFrame(rid);
rid = null;
}
} else {
spinning = true;
Animation();
}
is_spinning.innerHTML = spinning;
});
function Animation() {
rid = requestAnimationFrame(Animation);
if (currentAngle <= targetAngle) {
let speed = (targetAngle - currentAngle)/10;
currentAngle += speed;
element.style.transform = `rotate(${currentAngle}deg)`;
if(speed <= 1){// if the speed is < 1 stop the wheel
cancelAnimationFrame(rid);
spinning = false;
}
} else {//stop the wheel
cancelAnimationFrame(rid);
spinning = false;
}
is_spinning.innerHTML = spinning;
}
#wheel {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 20%;
}
<img id="wheel" src="https://reel-mkt.com/templates/fortune_reel_lp/img/wheel.png">
<p>spinning = <span id="is_spinning">false</span></p>
**An observation"": Your question is tagged as canvas but in your code you positioned the image in the center of the screen. So I assumed that you wanted to rotate the image.

Related

How to optimize particles with javascript

I'm creating particles for the first time with javascript and I'm not sure if the below code is optimized.
When I create 100 particles on the screen I don't notice that much of an fps drop.
When multiple clicks happen in a row the fps takes major dip.
This is understandable but is there a way to optimize this code more so that multiple clicks will maintain a higher frame rate?
var fps = document.getElementById("fps");
var startTime = Date.now();
var frame = 0;
function tick() {
var time = Date.now();
frame++;
if (time - startTime > 1000) {
fps.innerHTML = (frame / ((time - startTime) / 1000)).toFixed(1);
startTime = time;
frame = 0;
}
window.requestAnimationFrame(tick);
}
tick();
function pop (target) {
let amount = 100;
// Quick check if user clicked the button using a keyboard
const bbox = target.getBoundingClientRect();
const x = bbox.left + bbox.width / 2;
const y = bbox.top + bbox.height / 2;
for (let i = 0; i < 100; i++) {
createParticle(x, y);
}
}
function createParticle (x, y) {
const particle = document.createElement('particle');
document.body.appendChild(particle);
let width = Math.floor(Math.random() * 30 + 8);
let height = width;
let destinationX = (Math.random() - 0.5) * 1000;
let destinationY = (Math.random() - 0.5) * 1000;
let rotation = Math.random() * 2000;
let delay = Math.random() * 200;
particle.style.background = `hsl(${Math.random() * 90 + 270}, 70%, 60%)`;
particle.style.border = '1px solid white';
particle.style.width = `${width}px`;
particle.style.height = `${height}px`;
const animation = particle.animate([
{
transform: `translate(-50%, -50%) translate(${x}px, ${y}px) rotate(0deg)`,
opacity: 1
},
{
transform: `translate(-50%, -50%) translate(${x + destinationX}px, ${y + destinationY}px) rotate(${rotation}deg)`,
opacity: 0
}
], {
duration: Math.random() * 1000 + 5000,
easing: 'cubic-bezier(0, .9, .57, 1)',
delay: delay
});
animation.onfinish = removeParticle;
}
function removeParticle (e) {
e.srcElement.effect.target.remove();
}
document.querySelector(".confetti").addEventListener("click",()=>{pop(document.querySelector(".confetti"));}
);
particle {
position: fixed;
top: 0;
left: 0;
opacity: 0;
pointer-events: none;
background-repeat: no-repeat;
background-size: contain;
}
<div class="wrapper">
<span id="fps">--</span> FPS</div>
<div class="confetti">Square particles</div>
</div>
It would be much quicker if you could add a canvas and instead of adding the elements and styling them, you could just draw things onto the canvas. Then, it can be way faster. If you don't get how to use a canvas with JavaScript, you might want to search it up on something like W3Schools, because they have lots of things you could learn from them.
I hope this helps. Please give me any feedback if you have any. I am new to Stack Overflow, so I want to know how to improve. Thanks and happy coding!

Rotating divs with javascript on mouseenter and rotating them back to original position on mouseleave

My goal is to make div rotate when mouse enters it and rotate it back to the original position when mouse leaves. It works ok when I interact with only one div, but when I try hovering over other divs, it starts glitching.
JS
$(document).ready(function(){
var blocks = document.getElementsByClassName('block-item');
for (var i = 0, max = blocks.length; i < max; i ++) {
blocks[i].onmouseenter = blocks[i].onmouseleave = handler;
}
var startRotate = function(blackFrame, blackFrameAngle) {
// ...
};
var startRotateBack = function(blackFrame, blackFrameAngle) {
// ...
};
function handler(event) {
// If mouse enters start rotating clock-wise
if (event.type == 'mouseenter') {
// Calculate angles
var blackFrame = event.target.querySelectorAll('.block-frame')[0];
var blackFramePos = getComputedStyle(blackFrame).getPropertyValue('transform');
var blackFrameValues = blackFramePos.split('(')[1],
blackFrameValues = blackFrameValues.split(')')[0],
blackFrameValues = blackFrameValues.split(',');
var a = blackFrameValues[0];
var b = blackFrameValues[1];
var radians = Math.atan2(b, a);
if ( radians < 0 ) {
radians += (2 * Math.PI);
}
var blackFrameAngle = Math.round( radians * (180/Math.PI));
// Start rotating
startRotate(blackFrame, blackFrameAngle);
}
// If mouse leaves stop rotating clock-wise and start rotating back
if (event.type == 'mouseleave') {
clearInterval(RotateFrame)
// ...
// ...
// Start rotating back
startRotateBack(blackFrame, blackFrameAngle);
}
}
});
Full code is in jsfiddle http://jsfiddle.net/fw3ma9ej/2/
We can solve it just by using the CSS transition property and change the rotate angle of the element on hover.
you can see an example here: http://jsfiddle.net/j10y98w2/1/
transform: rotate(45deg);
transition: transform 5s ease-out;
&:hover {
transform: rotate(315deg);
}

Why is an <a> stealing mouse clicks from a <canvas>?

I'm trying to build a 3D piano using three.js; see the code snippet below.
let renderer, camera, scene, light, keys, selectedKey;
init();
render();
function init() {
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.querySelector(".app").appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight);
camera.position.z = 400;
scene = new THREE.Scene();
light = new THREE.SpotLight("#777777");
light.position.y = 800;
scene.add(light);
keys = new THREE.Group();
for (let i = 0; i < 15; i++) {
let key = new THREE.Mesh();
key.geometry = new THREE.BoxGeometry(30, 30, 130);
key.material = new THREE.MeshPhysicalMaterial({ color: "#dddddd", emissive: "#888888" });
key.position.x = (key.geometry.parameters.width + 2) * i;
key.rotation.x = Math.PI / 4;
keys.add(key);
}
scene.add(keys);
// Center the keys.
new THREE.Box3().setFromObject(keys).getCenter(keys.position).multiplyScalar(-1);
// Add mouse listeners.
window.addEventListener("mousedown", onMouseDown);
window.addEventListener("mouseup", onMouseUp);
window.addEventListener("mousemove", onMouseMove);
renderer.domElement.addEventListener("mouseleave", onMouseLeave);
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function onMouseDown(event) {
unpressKey(); // In case the previous mousedown event wasn't followed by a mouseup, force a key unpress now.
if (event.buttons !== 1) return; // Only accept left mouse button clicks.
selectedKey = keys.children.find(key => isKeyAtCoord(key, event.clientX, event.clientY));
pressKey();
}
function onMouseUp(event) {
unpressKey();
}
function onMouseMove(event) {
let key = keys.children.find(key => isKeyAtCoord(key, event.clientX, event.clientY));
renderer.domElement.style.cursor = key ? "pointer" : null;
// If a key was previously selected and the mouse has moved to another key, make that
// the new "selected" key. This allows keys to be pressed in a click+drag manner.
if (selectedKey && key && selectedKey !== key) {
unpressKey();
selectedKey = key;
pressKey();
}
}
function onMouseLeave(event) {
unpressKey(); // The mouse left the canvas, so cancel the last click.
}
function pressKey() {
if (selectedKey) {
selectedKey.position.y -= selectedKey.geometry.parameters.height / 2;
}
}
function unpressKey() {
if (selectedKey) {
selectedKey.position.y += selectedKey.geometry.parameters.height / 2;
selectedKey = null;
}
}
function isKeyAtCoord(key, x, y) {
x = (x / renderer.domElement.clientWidth) * 2 - 1;
y = -(y / renderer.domElement.clientHeight) * 2 + 1;
let raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
return raycaster.intersectObject(key).length > 0;
}
body, .app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
background: #ff6600;
}
.fork img {
position: absolute;
top: 0;
right: 0;
border: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.js">
</script>
<div class="app">
</div>
<a class="fork" href="https://github.com">
<img src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
</a>
If you play around with the piano keys long enough, you'll notice that sometimes the fork link in the top-right corner hijacks mouse clicks made on the keys (which are inside a <canvas>), leading to buggy behaviour. In case that's not clear, here's a GIF demonstrating this bug:
I've yet to determine what the cause of this is, and am having a hard time reproducing it consistently (sometimes you just have to click the keys for a while and wait for it to happen). I think it has something to do with the position: absolute of the link, or maybe the z-index / tabindex. But tweaking these values hasn't solved the problem so far.
Does anyone know what could be causing this and how I can fix it?
UPDATE: I've found that setting .fork img { to .fork { in the stylesheet reproduces the bug more often. Hopefully this helps someone figure out a solution.
Well, I found that adding user-select: none; to .fork's CSS fixes the bug. But it's more of a workaround than something that directly addresses the issue, so I'm not going to accept this as an answer. I hope someone else can chime in with a proper solution...

Text is vertically stretched on my canvas, making it unreadable

I have a canvas that I am drawing on top of a OpenLayers map. The canvas is colored in gradient colors and now I am trying to add text on top of it.
However the text seems to be stretched making it completely unreadable.
function createSpeedBar(min, max, speeds) {
//List of integer speeds
var fullSpeeds = [];
//List of unique speed values (no duplicates)
var uniqueSpeeds = [];
//Filling fullSpeeds using minimum and maximum values
for (i = min; i <= max; i++) {
fullSpeeds.push(i);
}
//Filling uniqueSpeeds with unique values
$.each(speeds, function (i, el) {
if ($.inArray(el, uniqueSpeeds) === -1) uniqueSpeeds.push(el);
});
//Sorting uniqueSpeeds (low to high)
uniqueSpeeds = uniqueSpeeds.sort(function (a, b) {
return a - b;
});
//Getting canvas element
var canvas = document.getElementById("speedList");
var context = canvas.getContext("2d");
context.rect(0, 0, canvas.width, canvas.height);
var grd = context.createLinearGradient(0, 0, 0, 170);
var hslMin = 0;
var hslMax = 240;
//Defining hslColors using uniqueSpeeds
var hslValues = uniqueSpeeds.map(function (value) {
if ($.inArray(value, fullSpeeds)){
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: true,
speed: value
};
} else {
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: false
};
};
});
var count = 1;
var length = hslValues.length;
//Gradient coloring using hslColors
hslValues.forEach(function (value) {
var color = 'hsl(' + value.h + ',' + value.s + '%,' + value.l + '%)';
grd.addColorStop(count / length, color)
count += 1;
});
context.fillStyle = grd;
context.fill();
//Setting up coloring and drawing of text
count = 1
var height = canvas.height;
var width = canvas.width;
var elementHeight = height / length;
//Drawing text on canvas
hslValues.forEach(function (value) {
context.font = "12px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "black";
if (value.full === true) {
context.fillText(value.speed, width / 2, ((elementHeight / 2) + elementHeight * count));
}
count += 1;
});
}
As it might be clear I am trying to create a bar displaying the intensities of the speed on the map where I have colored some markers. However the text on the canvas comes out like this:
Right now I have made the height of the canvas inherit the height of the map which is 500. The width of the canvas is set manually using css:
html
<div id="map" class="map">
<canvas id="speedList"></canvas>
</div>
css
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
width: 20px;
z-index: 1000;
height: inherit;
margin: 0;
padding: 0;
}
I am currently working on a Fiddle, but it takes a little time to reproduce, and I bet the issue is not that big, so hopefully someone knows how to fix it before I finish the Fiddle.
The main problem here is the CSS adjustment of the width and height of the canvas element.
If it helps to understand the problem, think of <canvas> the same way you would think of <img/>, if you take an img, and give it a width and height of 50 x 500, it would stretch too.
The fix is to ensure that you set the width an height of the canvas element itself, before it processes it's content, like this:
<canvas id="speedList" width="20" height="500"></canvas>
You then also need to make sure your remove the width and height properties inside your CSS, like this:
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
z-index: 1000;
margin: 0;
padding: 0;
}

Some bugs in code using Canvas

I have a scratch card simulator.
The user should be able to click and drag in order to reveal the text underneath.
I have 2 bugs with this implementaion:
a) Sometimes the scratch card clears itself as soon as the cursor enters the canvas from left or right. It should only clear itself when most of the card has been scratched off. Currently, it only works if the user moves their cursor in from the top border, going downwards.
b) Sometimes the scratch card will not work at all, or the scratching will be offset from the cursor, but only when the browser window is smaller than the document size (e.g., browser window is 300px wide but the body has a min-width of 900px or something and the user had to scroll the card into view).
(function () {
"use strict";
var container = document.getElementById('cbox-canvas'),
arrow = document.getElementById('cbox-arrow'),
textOne = document.getElementById('cbox-text-1'),
textTwo = document.getElementById('cbox-text-2'),
boxOne = document.getElementById('cbox-box-1'),
boxTwo = document.getElementById('cbox-box-2'),
cnv = container.getElementsByTagName('canvas'),
imageCover;
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.context = canvas.node.getContext('2d');
canvas.node.width = width || 100;
canvas.node.height = height || 100;
parent.appendChild(canvas.node);
return canvas;
}
function init(container, width, height, fillColor) {
var canvas = createCanvas(container, width, height),
ctx = canvas.context;
// define a custom fillCircle method
ctx.fillCircle = function (x, y, radius, fillColor) {
//this.fillStyle = fillColor;
this.shadowBlur = 15;
this.shadowOffsetX = 0;
this.shadowOffsetY = 0;
this.shadowColor = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI * 2, false);
this.fill();
this.stroke();
};
ctx.clearTo = function (fillColor) {
var imageObj = new Image();
imageObj.onload = function () {
ctx.drawImage(imageObj, 0, 0);
};
imageObj.src = fillColor;
};
ctx.clearTo(fillColor || "#ddd");
// bind mouse events
canvas.node.onmousemove = function (e) {
var canvasRect = container.getBoundingClientRect(),
x = e.pageX - canvasRect.left,
y = e.pageY - canvasRect.top,
radius = 30,
calc = 0;
fillColor = '#ff0000';
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
calc += x;
if (calc > 330 || calc < 6) {
container.removeChild(cnv[0]);
arrow.className += " slide-it";
textOne.className += " reveal-it";
textTwo.className += " fade-in";
boxOne.className += " fade-in-two";
boxTwo.className += " fade-in-one";
}
};
container.onmousemove = function (e) {
var canvasRect = container.getBoundingClientRect(),
mouseX = e.pageX || e.clientX,
mouseY = e.pageY || e.clientY,
relMouseX = mouseX - canvasRect.left,
relMouseY = mouseY - canvasRect.top,
leftLimit = 37,
topLimit = 37,
rightLimit = 25,
bottomLimit = 44,
x = e.pageX - canvasRect.left,
y = e.pageY - canvasRect.top,
radius = 25;
fillColor = '#ff0000';
if (relMouseX < leftLimit) {
relMouseX = leftLimit;
}
if (relMouseY < topLimit) {
relMouseY = topLimit;
}
if (relMouseX > width - rightLimit) {
relMouseX = width - rightLimit;
}
if (relMouseY > height - bottomLimit) {
relMouseY = height - bottomLimit;
}
if (!canvas.isDrawing) {
return;
}
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
};
}
imageCover = "images/scratch.png";
init(container, 369, 371, imageCover);
}());
https://jsfiddle.net/p05kg0vq/
Problems
There are several issues here:
You are loading the clear image for each time which happens asynchronously and it may not appear in time.
The same method, clearTo(), also takes a fill-style color, but tries to load it as image
You are listening to mouse move events two places which is not needed
For improvement: You're listening to mouse move events on the canvas. This is not wrong, but it will be more fluid using the window object
You're using pageX/Y to calculate mouse position. These are relative to page not client. getBoundingClientRect() is relative to client.
Not sure how you intend to calculate coverage
There are additional room for refactoring.
Solutions
Load image once globally and use the object as argument instead of the URL.
Differentiate between an image and color string. To do this check if argument is a string, if so, set fillStyle and use fillRect() to clear. If not use drawImage().
Remove the event from the container. It's not needed and will conflict with the second listener.
Use window.onmousemove instead (not required, but a better option in this case as it will move the cursor completely outside canvas - optionally use a parent node that is wider - it's up to you...).
Calculate using clientX and clientY instead and always.
Extract the ImageData and count pixels with no alpha data (=0). Then divide this count on total number of pixels to get percentage of coverage. This is fast (I'll show how below).
Left to OP to improve :)
So, lets modify the structure a bit. This is not optimal, but meant to get you started. Load the image once and globally (or within the parent scope so the object is accessible).
// preload image once
var imageCover = "//i.imgur.com/b4m1M1n.png"; // needed cors for demo
var imageObj = new Image();
imageObj.onload = go;
imageObj.crossOrigin = ""; // for demo, for getImageData to work
imageObj.src = imageCover;
function go() {
/* ... inner code not shown ... */
init(container, 369, 371, imageObj);
};
Then rewrite clearTo() to accept both image and fill style. Notice this may break browser optimizations as there are two different types involved, but in this case it likely doesn't matter:
ctx.clearTo = function(fillColor) {
if (typeof fillColor === "string") { // is a string?
ctx.fillStyle = fillColor; // set as fill style
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
else { // assumes an image if not string
ctx.drawImage(fillColor, 0, 0);
}
};
ctx.clearTo(fillColor || "#ddd");
Then move onmousemove to window object and use clientX/clientY:
window.onmousemove = function(e) {
var canvasRect = container.getBoundingClientRect(),
x = e.clientX - canvasRect.left, // use clientX/Y (pageXY is unofficial)
y = e.clientY - canvasRect.top,
/* ... */
Within the same code block provide a function calculate real-time coverage of canvas:
// calc converage and clean if < 20%
if (calcCover(ctx) < 0.2) {
// end, reveal, etc.
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
console.log("DONE");
};
The function used here does:
function calcCover(ctx) {
var w = ctx.canvas.width, // just to cache width/height
h = ctx.canvas.height,
// convert Uint8ClampedArray to Uint32Array, no memory loss
// but faster
data32 = new Uint32Array(ctx.getImageData(0,0,w,h).data.buffer),
count = w * h; // total number of pixels
// iterate, check for alpha-channel (0xAABBGGRR, little-endian format)
// for the Uint32Array data.
for(var i = 0; i < data32.length; i++) if (!(data32[i] & 0xff000000)) count--;
// convert to a percentage (or rather normalize)
return count / (w*h);
};
Now we are good to go:
Modified fiddle
Looking at your code I could only suggest a full rewrite
;(function () {
"use strict";
// Generic functions and constants
const PI2 = Math.PI * 2;
function applyStyle (ctx, style) { Object.keys(style).forEach(key => ctx[key] = style[key] ) }
function ease (val, power) { return val < 0 ? 0 : val > 1 ? 1 : Math.pow(val, power) }
// General settings
const settings = {
width : 369,
height : 371,
coveragedMin : 0.2, // when to uncover all out of 1
coverColor : "#ddd", // colour to show on canvas while main image is loading. (not needed but to keep with you code)
mouseEvents : "mouseup,mousedown,mousemove".split(","), // list of mouse events to listen to
coverImage : loadImage("https://image.ibb.co/f8TNS5/scratch.png"), // the scratch image
container : document.getElementById('cbox-canvas'), // the container
drawStyle : { // the draw style of the revealing mouse moves. Note that this adds radius to the context but should not matter
radius : 20,
shadowBlur : 15,
shadowOffsetX : 0,
shadowOffsetY : 0,
shadowColor : "black",
fillStyle : "black",
globalCompositeOperation : "destination-out",
},
startAnim (){ // specific to this scratch reveal animations
document.getElementById("cbox-arrow").className = "cbox-arrow slide-it";
document.getElementById("cbox-text-1").className = "cbox-text-1 reveal-it";
document.getElementById("cbox-box-1").className = "cbox-box-1 fade-in-two";
document.getElementById("cbox-box-2").className = "cbox-box-2 fade-in-one";
document.getElementById("cbox-text-2").className = "cbox-text-2 fade-in";
},
coverageArray : (() => {const buf = new Uint8Array(64); buf.fill(1); return buf }) (), // array to is used to determine coverage
}
var update = true; // when true update canvas render
const mouse = { x : 0, y : 0, button : false}; // Mouse state
function mouseEvent (e) { // handles all mouse events
const canvasRect = settings.container.getBoundingClientRect();
mouse.x = e.pageX - canvasRect.left - scrollX;
mouse.y = e.pageY - canvasRect.top - scrollY;
if (e.type === "mousedown") { mouse.button = true }
else if (e.type === "mouseup") { mouse.button = false }
update = true; // flags that there needs to be a re render
}
function fillCircle (ctx, x, y, style) { // Draws a circle on context ctx, at location x,y using style
applyStyle(ctx, style);
ctx.beginPath();
ctx.arc(x, y, style.radius, 0, Math.PI * 2);
ctx.fill();
}
function setCoverage (array,x,y){ // Clears the coverage array, coordinates x,y are normalised 0-1
var i = array.length - 1; // and returns coverage as a value 0 no coverage to 1 full cover
const size = Math.sqrt(array.length) | 0;
array[(x * size) | 0 + ((y * size) | 0) * size] = 0;
var count = 0;
while(i-- > 0){ count += array[i] };
return count / array.length;
}
function loadImage (url) { // Loads an image and sets a property indicating if its has been rendered
const image = new Image();
image.src = url;
image.rendered = false;
return image;
}
function createCanvas (width, height) { // Creates a canvas of size width and height, set property ctx to the 2D context
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.ctx = canvas.getContext("2d");
return canvas;
}
(function (settings) { // Start the app
const canvas = createCanvas(settings.width, settings.height);
settings.container.appendChild(canvas);
const ctx = canvas.ctx;
settings.mouseEvents.forEach(eventName => addEventListener(eventName, mouseEvent)); // start the mouse
var reveal = false; // when true reveal the prize (yep suckers) ???
var fade = 1; // fades out the canvas
ctx.fillStyle = settings.coverColor; // cover while waiting for image to load
ctx.fillRect(0, 0, canvas.width, canvas.height); // image will not yet have loaded so cover image
(function mainLoop () { // main animation loop will play unt ill canvas faded out
if (settings.coverImage.complete && !settings.coverImage.rendered) { // wait till image has loaded
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(settings.coverImage, 0, 0, settings.width, settings.height);
settings.coverImage.rendered = true;
const swipeEl = document.getElementById("swipe-area");
swipeEl.className = "swipe-area loaded";
swipeEl.title = "Use your mouse to reveal your PRIZE :P";
}
if (update) { // only if needed render canvas
if (settings.coverImage.rendered) {
mouse.button && fillCircle(ctx, mouse.x, mouse.y, settings.drawStyle);
setCoverage(settings.coverageArray, mouse.x / settings.width, mouse.y / settings.height) < settings.coveragedMin && (reveal = true);
update = false;
}
if (reveal) {
fade -= 0.05;
canvas.style.opacity = ease(fade,2);
update = true; // need continuous update for animation
}
}
if (reveal && fade <= 0) { // scratching all done remove canvas, mouse events and start any animations. Do not call requestAnimationFrame as all done.
const swipeEl = document.getElementById("swipe-area");
swipeEl.style.cursor = "pointer";
swipeEl.title = "Click here to collect your $$$$";
settings.container.removeChild(canvas);
settings.mouseEvents.forEach(eventName => removeEventListener(eventName, mouseEvent));
settings.startAnim();
// All done. All objects should now have no references (important to remove mouse and requestAnimation frame) and any other functions
// that can hold a closure
} else {
requestAnimationFrame(mainLoop);
}
} () );
} (settings) );
} () );
/*SWIPE*/
.swipe-area {
position: absolute;
width: 369px;
height: 371px;
left: 10px;
top: 10px;
z-index: 15;
background-size: 100%;
}
.preload {
cursor : wait;
background: url('https://image.ibb.co/f8TNS5/scratch.png') no-repeat;
background-size: 100%;
}
.loaded {
cursor : url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAbCAYAAABm409WAAABkklEQVRIibXWPWtUQRjF8d/GbDRFomghZFNorREMiIUgpEgRSFJFuyAWNoovTQIqdvZp8lXCBb+ApZ9AApb2lmFS3Jns3XVm9ya7+8DDwMA9/3vOM/eFfC0W9qdSodHTFz46FkIQfv4S8GdaoCQY6AMaoIkgoapqodQJkgCTQgbE/4ecTOwkC+hDTi76qpAiYBAysHc6M0gIM3MxOPjLQkYCcpBLA4aPagHyNwGqasYurgRo6SIH6UzVRVX1OwKWcAMLmBsFOGvrYgiwhvtYxU3MTxRVxsVvbGEDD3Ab10qAzriomoCj4wsX+9jDM/RiXNmax+NxTsRvRwNwgI94GSNbUhj+Qszx7SgnCdCYwzd8xms8wS2FgSfAOr4348jF1BD/isMY1XrUyAK60d4aXuCH/NFMfYgvcf3UJqK56KKnHtge3uDfkPAHvI/rO7yKNzR2yKkWsYKHeI5NbGMHu3HdiXubeIpHUbz1b0+K62688F6he7iD5TZ3Plwd9dHt4nqhu+oHq/U7KQfpqOeT66LwOUobOkEQCJp1AAAAAElFTkSuQmCC') 9 20, pointer;
background: url('https://image.ibb.co/j4j7uk/sc_bg.png') no-repeat;
background-size: 100%;
}
.anim-container {
position: absolute;
width: 360px;
height: 366px;
right: 5px;
top: 5px;
z-index: -1;
background-size: 100%;
overflow: hidden;
}
.cbox-arrow {
position: absolute;
left: 56px;
top: 0;
z-index: -1;
width: 260px;
height: 264px;
background: url('https://image.ibb.co/fXKwn5/arrow.png') no-repeat;
background-size: 100%;
-webkit-animation: 10s slide;
}
.cbox-text-1 {
position: absolute;
left: 72px;
top: 100px;
z-index: -1;
width: 230px;
height: 65px;
background: url('https://image.ibb.co/d6YYZk/test1.png') no-repeat;
background-size: 100%;
opacity: 1;
}
.cbox-text-2 {
position: absolute;
left: 72px;
top: 100px;
z-index: -1;
width: 230px;
height: 65px;
background: url('https://image.ibb.co/bCaQfQ/test2.png') no-repeat;
background-size: 100%;
opacity: 0;
}
.cbox-box-1 {
position: absolute;
left: 55px;
top: 167px;
z-index: -1;
width: 257px;
height: 65px;
background: url('https://image.ibb.co/fG7hS5/box1.png') no-repeat;
background-size: 100%;
opacity: 0;
}
.cbox-box-2 {
position: absolute;
left: 135px;
top: 124px;
z-index: -1;
width: 99px;
height: 127px;
background: url('https://image.ibb.co/dOSSuk/box2.png') no-repeat;
background-size: 100%;
opacity: 0;
}
.hidden { display: none;}
/* unknowns */
.newslisting, #sidebar1Bottom { background: #ffffff !important; }
.tmx header { height: 269px;}
/*Animations, you can add agent prescripts, though we should never have to do that */
.fade-in { animation: 1.5s 2.5s fade;}
.fade-in-one { animation: 2.5s 5s fade;}
.fade-in-two { animation: 2.5s 7.5s fade-alt forwards;}
.fade-in-three { animation: 5s 15s fade;}
.reveal-it { animation: 2.5s reveal forwards;}
.slide-it { animation: 5s slide-in forwards;}
#keyframes fade-alt {0% { opacity: 0; } 10% {opacity : 1;} 100% { opacity: 1;} }
#keyframes fade { 0% { opacity: 0; } 10% {opacity: 1;} 90% {opacity: 1;} 100% {opacity: 0;} }
#keyframes reveal {0% { opacity: 1;} 80% { opacity: 1;} 100% { opacity: 0;} }
#keyframes slide-in {0% { top: 5px; } 80% { top: 5px; } 100% { top: -150px;} }
<div class="swipe-area preload" id="swipe-area" title="Just a moment as we asses your gullibility!">
<div id="cbox-canvas">
<div class="anim-container">
<div id="cbox-arrow" class="hidden"></div>
<div id="cbox-text-1" class="hidden"></div>
<div id="cbox-box-1" class="hidden"></div>
<div id="cbox-box-2" class="hidden"></div>
<div id="cbox-text-2" class="hidden"></div>
</div>
</div>
</div>

Categories