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

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...

Related

pixi viewport drag bad working after select a child

When I drag on an empty stage, it moves fine.
But when I do this on my children, it gets annoying.
import { Application, Sprite, Texture } from 'pixijs';
import { Viewport } from 'pixi-viewport'
const app = new Application({
backgroundAlpha: 1,
width: innerWidth, height: innerHeight,
antialias: true,
})
document.body.appendChild(app.view)
const stage = new Viewport({
screenWidth: window.innerWidth,
screenHeight: window.innerHeight,
worldHeight: 1000,
worldWidth: 1000,
})
app.stage.addChild(stage)
stage.drag().pinch().wheel().decelerate().setZoom(0.5).clampZoom({ maxScale: 2, minScale: 0.3 }).interactiveChildren = true
for (let x = 0; x < 25; x++) {
for (let y = 0; y < 25; y++) {
Spawn(x, y)
}
}
function Spawn(x, y) {
const box = Sprite.from(Texture.WHITE)
box.position.set(x * Texture.WHITE.width, y * Texture.WHITE.height)
box.scale.set(0.90)
box.on('click', () => console.log('box clicked!'))
box.interactive = true
stage.addChild(box)
}
Video of the problem
I want to move on the boxes and click on them as well.
You can prevent the propagation of pointerdown events on the boxes. As a result, the viewport dragging will never start since the viewport will not receive said event:
box.on('pointerdown', (e) => {
e.stopPropagation();
});
This is the related documentation.

Moving element intersecting static element in CSS/Javascript

Video demo
I'm trying to make a rip-off pacman(ish) game for a project using HTML, CSS and Javascript. Check the attached video for reference. The idea is: whenever one of the food elements will intersect the pacman element (ie. get eaten), the score will increase by one. And whenever a food element gets out of the viewport without going through the pacman element, the player will lose 1 life. I tried implementing this with the Intersection Observer API by observing the food elements as target and making the pacman element the root. But that didn't work.
const obsCB1 = function (entries, observer) {
point++;
console.log(point);
};
const obsOptions1 = {
root: pacman,
threshold: 0.01,
};
const obs1 = new IntersectionObserver(obsCB1, obsOptions1);
obs1.observe(f1);
(The food element initially starts from outside the viewport to the right and moves to the left using translateX in an infinite loop)
The callback was called once at the start even when f1 didn't intersect pacman. And the point didn't change afterwards. How can I implement this functionality?
Assuming both pacmen and the dots are circles, I guess the best is to calculate the distance between to center and check if it less the sum of radiuses
const board = document.querySelector('#board');
const score = board.querySelector('#score-val');
let dots = [];
function updateScore(x){score.innerText = +score.innerText+x}
function Dot(){
this.SPEED = (.008 + Math.random()/1000) * window.innerHeight;
this.RADIUS = .02 * window.innerHeight;
this.el = document.createElement('span');
this.el.style = `display: inline-block;width:${2*this.RADIUS}px;height:${2*this.RADIUS}px;position:absolute;left:${window.innerWidth}px;top:${Math.random()*(window.innerHeight-2*this.RADIUS)}px;border-radius:50%;background-color: red;`;
board.appendChild(this.el);
dots.push(this);
this.remove = () => {
dots = dots.filter(d => d!== this);
this.el.remove();
}
this.move = () => {
this.el.style.left = parseFloat(this.el.style.left)-this.SPEED+'px';
if(pacmen.isCollapse(this)){
this.remove();
updateScore(1);
} else
if(parseFloat(this.el.style.left)<0) { this.remove();
updateScore(-1);
}
}
}
function Pacmen(){
this.SPEED = .05 * window.innerHeight;
this.RADIUS = .07 * window.innerHeight;
this.el = board.querySelector('#pacmen');
this.el.style.left=0;
this.el.style.top=0;
this.el.width=this.RADIUS*2;
this.el.height=this.RADIUS*2;
this.move = x => {
this.el.style.top = Math.min(window.innerHeight-this.RADIUS*2, Math.max(0, parseFloat(this.el.style.top)+x*this.SPEED))+'px';
}
window.addEventListener('keydown', (e)=> {
switch(e.key){
case 'ArrowDown':
e.preventDefault();
this.move(1);
break;
case 'ArrowUp':
e.preventDefault();
this.move(-1);
break;
}
});
this.isCollapse = (dot) => {
return Math.sqrt(Math.pow(parseFloat(this.el.style.left + this.RADIUS)-parseFloat(dot.el.style.left + dot.RADIUS),2)+Math.pow(parseFloat(this.el.style.top + this.RADIUS)-parseFloat(dot.el.style.top + dot.RADIUS),2)) <= this.RADIUS + dot.RADIUS
}
}
const pacmen = new Pacmen();
function frame(){
dots.forEach(dot =>dot.move());
requestAnimationFrame(frame);
}
setInterval(()=>new Dot(), 1000);
frame();
html,body{padding:0;margin:0}
#board{
display:block;
width:100%;
height:100vh;
background-color:black;
position: relative;
overflow:hidden;
}
#pacmen {
position: absolute;
}
#score{
position:absolute;
bottom: 0px;
right: 20px;
color: white;
font-family: sans-serif;
}
<div id="board">
<img id="pacmen" src="data:image/gif;base64,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"/>
<h3 id="score">Score: <span id="score-val">0</span><h3>
</div>

I want to be able to pause a block in place when I click on it

Here is the website with the game/source code and want to try and see if i can pause a block as it falls when i left click it with my mouse but not sure the proper function for it. ( https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/By_example/Raining_rectangles )
You must clearTimeout() to make it paused, I have implemented a toggle on click of box i.e play/pause.
(function() {
"use strict"
window.addEventListener("load", setupAnimation, false);
var gl,
timer,
rainingRect,
scoreDisplay,
missesDisplay,
status,
paused = false;
function setupAnimation(evt) {
window.removeEventListener(evt.type, setupAnimation, false);
if (!(gl = getRenderingContext()))
return;
gl.enable(gl.SCISSOR_TEST);
rainingRect = new Rectangle();
timer = setTimeout(drawAnimation, 17);
document.querySelector("canvas")
.addEventListener("click", playerClick, false);
var displays = document.querySelectorAll("strong");
scoreDisplay = displays[0];
missesDisplay = displays[1];
status = displays[2];
}
var score = 0,
misses = 0;
function drawAnimation() {
gl.scissor(rainingRect.position[0], rainingRect.position[1],
rainingRect.size[0], rainingRect.size[1]);
gl.clear(gl.COLOR_BUFFER_BIT);
rainingRect.position[1] -= rainingRect.velocity;
if (rainingRect.position[1] < 0) {
misses += 1;
missesDisplay.innerHTML = misses;
rainingRect = new Rectangle();
}
// We are using setTimeout for animation. So we reschedule
// the timeout to call drawAnimation again in 17ms.
// Otherwise we won't get any animation.
timer = setTimeout(drawAnimation, 17);
}
function playerClick(evt) {
// We need to transform the position of the click event from
// window coordinates to relative position inside the canvas.
// In addition we need to remember that vertical position in
// WebGL increases from bottom to top, unlike in the browser
// window.
var position = [
evt.pageX - evt.target.offsetLeft,
gl.drawingBufferHeight - (evt.pageY - evt.target.offsetTop),
];
// if the click falls inside the rectangle, we caught it.
// Increment score and create a new rectangle.
var diffPos = [position[0] - rainingRect.position[0],
position[1] - rainingRect.position[1]
];
if (diffPos[0] >= 0 && diffPos[0] < rainingRect.size[0] &&
diffPos[1] >= 0 && diffPos[1] < rainingRect.size[1]) {
score += 1;
scoreDisplay.innerHTML = score;
// rainingRect = new Rectangle();
if (!paused) {
clearTimeout(timer)
paused = true;
status.innerHTML = 'Paused';
} else {
timer = setTimeout(drawAnimation, 17);
paused = false;
status.innerHTML = 'Playing';
}
}
}
function Rectangle() {
// Keeping a reference to the new Rectangle object, rather
// than using the confusing this keyword.
var rect = this;
// We get three random numbers and use them for new rectangle
// size and position. For each we use a different number,
// because we want horizontal size, vertical size and
// position to be determined independently.
var randNums = getRandomVector();
rect.size = [
5 + 120 * randNums[0],
5 + 120 * randNums[1]
];
rect.position = [
randNums[2] * (gl.drawingBufferWidth - rect.size[0]),
gl.drawingBufferHeight
];
rect.velocity = 1.0 + 6.0 * Math.random();
rect.color = getRandomVector();
gl.clearColor(rect.color[0], rect.color[1], rect.color[2], 1.0);
function getRandomVector() {
return [Math.random(), Math.random(), Math.random()];
}
}
function getRenderingContext() {
var canvas = document.querySelector("canvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var gl = canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl");
if (!gl) {
var paragraph = document.querySelector("p");
paragraph.innerHTML = "Failed to get WebGL context." +
"Your browser or device may not support WebGL.";
return null;
}
gl.viewport(0, 0,
gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
return gl;
}
})();
<style>
body {
text-align: center;
}
canvas {
display: block;
width: 280px;
height: 210px;
margin: auto;
padding: 0;
border: none;
background-color: black;
}
button {
display: block;
font-size: inherit;
margin: auto;
padding: 0.6em;
}
</style>
<p>You caught
<strong>0</strong>.
You missed
<strong>0</strong>
Status
<strong>Playing</strong>.</p>
<canvas>Your browser does not seem to support
HTML5 canvas.</canvas>
As you can see in the code the function drawAnimation() is calling itself every 17ms using setTimeout() JavaScript function (and this is what creates steady animation).
function drawAnimation() {
.
.
.
timer = setTimeout(drawAnimation, 17);
}
In order to pause/stop the animation you would need to use JavaScript function clearTimeout(timer). Since you want to stop/pause the animation on click event you could just reuse the function playerClick (evt) { ... } from the code you already have and put the function clearTimeout(timer) there.
function playerClick (evt) {
.
.
.
clearTimeout(timer);
}
If you want to be able to continue with animation after you have paused it you'll need to implement some switch-logic (pause if it is already playing, play if it is already paused) inside your function playerClick (evt) or to use timers to continue the animation after some time, for example.

Trying to provide webgl with a random color when the user clicks on a falling block

When the user clicks on the falling block it pauses, and when clicked on again it unpauses. I am trying to figure out how to make it so that once you pause the block it changes to a random color and then when unpaused it will then change again to a another random color. I am not too familiar with WebGL. Here is the source code.
Would I need to add a render function?
and then add
function checkclickPressed(e) {
if (e.keyCode == "0") {
color = [Math.random(), Math.random(), Math.random(), 1];
alert("left click pressed");
requestAnimationFrame( render );
}
}
Here's a dirty solution.
Your falling block's color is determined by:
rainingRect.color = [Math.random(), Math.random(), Math.random()];
gl.clearColor(rainingRect.color[0], rainingRect.color[1], rainingRect.color[2], 1.0);
Inside your
if (!paused) {
}
code block - where you update the score and pause the game, you could simply add the above lines to change the color. The problem is, that your drawAnimation() function doesn't get called anymore so your canvas wouldn't be updated with the new color.
Furthermore, if we simply call drawAnimation inside the if-block it would start firing every 17ms again because of the setTimeout timer inside.
So I recommend adding a paramter which controls if the timer should restart or not.
function drawAnimation(repeat) {
gl.scissor(rainingRect.position[0], rainingRect.position[1],
rainingRect.size[0], rainingRect.size[1]);
gl.clear(gl.COLOR_BUFFER_BIT);
rainingRect.position[1] -= rainingRect.velocity;
if (rainingRect.position[1] < 0) {
misses += 1;
missesDisplay.innerHTML = misses;
rainingRect = new Rectangle();
}
// We are using setTimeout for animation. So we reschedule
// the timeout to call drawAnimation again in 17ms.
// Otherwise we won't get any animation.
if (repeat) {
timer = setTimeout(drawAnimation, 17, true);
}
}
Now you can safely call drawAnimation(false); to update your canvas once. Just remember to make it true for all other calls to drawAnimation.
So after setting the color to a random new one and calling drawAnimation, you have to set a new color once more. This will be visible as soon as the game continues.
(function() {
"use strict"
window.addEventListener("load", setupAnimation, false);
var gl,
timer,
rainingRect,
scoreDisplay,
missesDisplay,
status,
paused = false;
function setupAnimation(evt) {
window.removeEventListener(evt.type, setupAnimation, false);
if (!(gl = getRenderingContext()))
return;
gl.enable(gl.SCISSOR_TEST);
rainingRect = new Rectangle();
timer = setTimeout(drawAnimation, 17, true);
document.querySelector("canvas")
.addEventListener("click", playerClick, false);
var displays = document.querySelectorAll("strong");
scoreDisplay = displays[0];
missesDisplay = displays[1];
status = displays[2];
}
var score = 0,
misses = 0;
function drawAnimation(repeat) {
gl.scissor(rainingRect.position[0], rainingRect.position[1],
rainingRect.size[0], rainingRect.size[1]);
gl.clear(gl.COLOR_BUFFER_BIT);
rainingRect.position[1] -= rainingRect.velocity;
if (rainingRect.position[1] < 0) {
misses += 1;
missesDisplay.innerHTML = misses;
rainingRect = new Rectangle();
}
// We are using setTimeout for animation. So we reschedule
// the timeout to call drawAnimation again in 17ms.
// Otherwise we won't get any animation.
if (repeat) {
timer = setTimeout(drawAnimation, 17, true);
}
}
function playerClick(evt) {
// We need to transform the position of the click event from
// window coordinates to relative position inside the canvas.
// In addition we need to remember that vertical position in
// WebGL increases from bottom to top, unlike in the browser
// window.
var position = [
evt.pageX - evt.target.offsetLeft,
gl.drawingBufferHeight - (evt.pageY - evt.target.offsetTop),
];
// if the click falls inside the rectangle, we caught it.
// Increment score and create a new rectangle.
var diffPos = [position[0] - rainingRect.position[0],
position[1] - rainingRect.position[1]
];
if (diffPos[0] >= 0 && diffPos[0] < rainingRect.size[0] &&
diffPos[1] >= 0 && diffPos[1] < rainingRect.size[1]) {
score += 1;
scoreDisplay.innerHTML = score;
// rainingRect = new Rectangle();
if (!paused) {
clearTimeout(timer)
paused = true;
rainingRect.color = [Math.random(), Math.random(), Math.random()];
gl.clearColor(rainingRect.color[0], rainingRect.color[1], rainingRect.color[2], 1.0);
drawAnimation(false);
rainingRect.color = [Math.random(), Math.random(), Math.random()];
gl.clearColor(rainingRect.color[0], rainingRect.color[1], rainingRect.color[2], 1.0);
status.innerHTML = 'Paused';
} else {
timer = setTimeout(drawAnimation, 17, true);
paused = false;
status.innerHTML = 'Playing';
}
}
}
function Rectangle() {
// Keeping a reference to the new Rectangle object, rather
// than using the confusing this keyword.
var rect = this;
// We get three random numbers and use them for new rectangle
// size and position. For each we use a different number,
// because we want horizontal size, vertical size and
// position to be determined independently.
var randNums = getRandomVector();
rect.size = [
5 + 120 * randNums[0],
5 + 120 * randNums[1]
];
rect.position = [
randNums[2] * (gl.drawingBufferWidth - rect.size[0]),
gl.drawingBufferHeight
];
rect.velocity = 1.0 + 6.0 * Math.random();
rect.color = getRandomVector();
gl.clearColor(rect.color[0], rect.color[1], rect.color[2], 1.0);
function getRandomVector() {
return [Math.random(), Math.random(), Math.random()];
}
}
function getRenderingContext() {
var canvas = document.querySelector("canvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var gl = canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl");
if (!gl) {
var paragraph = document.querySelector("p");
paragraph.innerHTML = "Failed to get WebGL context." +
"Your browser or device may not support WebGL.";
return null;
}
gl.viewport(0, 0,
gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
return gl;
function checkKeyPressed(e) {
}
function checkclickPressed(e) {
}
}
})();
<style>body {
text-align: center;
}
canvas {
display: block;
width: 280px;
height: 210px;
margin: auto;
padding: 0;
border: none;
background-color: black;
}
button {
display: block;
font-size: inherit;
margin: auto;
padding: 0.6em;
}
</style>
<p>You caught
<strong>0</strong>. You missed
<strong>0</strong> Status
<strong>Playing</strong>.</p>
<canvas>Your browser does not seem to support
HTML5 canvas.</canvas>
Here's the full example - hit 'Run code snippet' to see it in action:

How do I see if one element is touching another in JavaScript without clicking anything

OK so I've tried one thing from a different question and it worked, but not the way I wanted it to. it didn't work the way I wanted it to! You literally had to click when two objects were touching so it would alert you, if somebody can figure out a way to detect if two elements are touching without having to click that would be a life saver! So I hope you people who read this request please respond if you know how. this is the code below. so one object is moving and i want it to make it stop when the object hits the player (i am making a game) the movement is by px.... i want it to keep testing if one object hits the player, and if it does i want it to stop everything.
var boxes = document.querySelectorAll('.box');
boxes.forEach(function (el) {
if (el.addEventListener) {
el.addEventListener('click', clickHandler);
} else {
el.attachEvent('onclick', clickHandler);
}
})
var detectOverlap = (function () {
function getPositions(elem) {
var pos = elem.getBoundingClientRect();
return [[pos.left, pos.right], [pos.top, pos.bottom]];
}
function comparePositions(p1, p2) {
var r1, r2;
if (p1[0] < p2[0]) {
r1 = p1;
r2 = p2;
} else {
r1 = p2;
r2 = p1;
}
return r1[1] > r2[0] || r1[0] === r2[0];
}
return function (a, b) {
var pos1 = getPositions(a),
pos2 = getPositions(b);
return comparePositions(pos1[0], pos2[0]) && comparePositions(pos1[1], pos2[1]);
};
})();
function clickHandler(e) {
var elem = e.target,
elems = document.querySelectorAll('.box'),
elemList = Array.prototype.slice.call(elems),
within = elemList.indexOf(elem),
touching = [];
if (within !== -1) {
elemList.splice(within, 1);
}
for (var i = 0; i < elemList.length; i++) {
if (detectOverlap(elem, elemList[i])) {
touching.push(elemList[i].id);
}
}
if (touching.length) {
console.log(elem.id + ' touches ' + touching.join(' and ') + '.');
alert(elem.id + ' touches ' + touching.join(' and ') + '.');
} else {
console.log(elem.id + ' touches nothing.');
alert(elem.id + ' touches nothing.');
}
}
this is my video game right now (please do not copy)
<!DOCTYPE html>
/
<html>
<form id="player" class="box">
</form>
<button type="button" class="up" onclick="moveup()">^</button>
<button type="button" class="down" onclick="movedown()">v
</button>
<style src="style.css">
#player {
width: 300px;
height: 100px;
background-color: blue;
display: inline-block;
position: relative;
bottom: -250px;
left: 200px;
}
.up {
position: relative;
bottom: -400px;
}
.down {
position: relative;
bottom: -420px;
}
body {
background-color: black;
}
#car {
width: 300px;
height: 100px;
background-color: red;
display: inline-block;
position: relative;
bottom: -250px;
left: 600px;
}
</style>
<form id="car" class="box"></form>
<script>
imgObj = document.getElementById('player');
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
function moveup() {
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
imgObj.style.bottom = parseInt(imgObj.style.bottom) + 70 + 'px';
}
function movedown() {
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
imgObj.style.bottom = parseInt(imgObj.style.bottom) + -120 + 'px';
}
myMove();
function myMove() {
var elem = document.getElementById("car");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 1000) {
clearInterval(id);
myMove();
} else {
pos++;
elem.style.left = pos + "px";
elem.style.left = pos + "px";
}
}
}
/* please do not copy; this is it so far i want the red box when it hits the player(blue box) to stop everything that is happening */
/* made by Jsscripter; car game */
</script>
</html>
Intersection observer. API was largely developed because of news feeds and infinite scrolling. Goal was to solve when something comes into view, load content. Also is a great fit for a game.
The Intersection Observer API lets code register a callback function
that is executed whenever an element they wish to monitor enters or
exits another element (or the viewport), or when the amount by which
the two intersect changes by a requested amount. This way, sites no
longer need to do anything on the main thread to watch for this kind
of element intersection, and the browser is free to optimize the
management of intersections as it sees fit.
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
All major browsers except safari support the API. For backwards compatibility and Safari support can use the polyfill from W3C found here. Check out this example from MDN:
var callback = function(entries, observer) {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
var options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);
var target = document.querySelector('#listItem');
observer.observe(target);
See this in action here: https://codepen.io/anon/pen/OqpeMV

Categories