How to have an element follow a dynamic moving element? - javascript

Okay, so I got most of this done. Here is the code:
function gi(a) {
return document.getElementById(a)
}
gameworld = document.getElementById('GameWorld');
character = document.getElementById('character');
var oldvX = 30;
var oldvY = 30;
gameworld.addEventListener('click', function(e) {
var offsetX = e.offsetX;
var offsetY = e.offsetY;
var element = e.target;
character.style.left = offsetX + 'px';
character.style.top = offsetY + 'px';
});
setInterval(function() {
monster = gi('monster');
oldvX += 10;
oldvY += 10;
gb = gameworld.getBoundingClientRect();
cb = character.getBoundingClientRect();
oldvX += cb.left / 10;
oldvY += cb.top / 10;
if (oldvX >= (cb.left)) {
return
}
if (oldvY >= (cb.top)) {
return
}
monster.style.left = oldvX + 'px';
monster.style.top = oldvY + 'px';
}, 500);
And here is the jsfiddle:
https://jsfiddle.net/g43nxhcw/12/
As you can see, the monster is making its way towards the player via the players' getBoundingClientRect(). My dilemma is: When I move around the red square, the monster is not following my character. I am not sure how to accomplish this or if this is even possible.

You should calculate a direction vector
vx = cb.left - parseInt(monster.style.left);
vy = cb.top - parseInt(monster.style.top);
vl = Math.sqrt(vx*vx+vy*vy);
vx = vx / vl
vy = vy / vl
it always have the length 1so you can multiply with the step size
vx = vx * 10
vy = vy * 10
and make it chase you like your little friend:
monster.style.left=(parseInt(monster.style.left) + vx) +'px';
monster.style.top=(parseInt(monster.style.top) + vy)+'px';
there is something wrong with your detection of collision also..
but I wont cover that here
https://jsfiddle.net/g43nxhcw/13/

Related

Pure Javascript particle repeller from base64 encoded png

I have some js code that i copied from a youtube tutorial and adapted for my own project to fill the header, the code works as intended and it works when the viewport is smaller than around 1200px, however when i put firefox into full screen the animation does not play & the image is being stretched, not retaining its aspect ratio. I do have a 10/15 year old gpu so i'm guessing thats half my issue. The script uses a png image file of 100x100 pixels, which it then converts into particle color values. Can this be optimized or made to run better. it seems that the wider the viewport the longer the animation takes to kick in, until it finally stops & doesn't work. full screen= [2550x1440]...
The original tutorial is here: Pure Javascript Particle Animations & to convert an image to base64 encoding is here: Image to base64.
HTML:
<html>
<body>
<canvas id="CanV"></canvas>
</body>
</html>
CSS:
#Canv{
position:absolute;
top:-1px;left:-2px;
z-index:67;
width:100vw !important;
max-height: 264px !important;
min-height: 245px !important;
filter:blur(2.27px);
}
Javascript:
window.addEventListener("DOMContentLoaded",(e)=>{
const canv = document.getElementById('Canv');
const ctx = canv.getContext('2d');
canv.width = window.innerWidth;
canv.height = window.innerHeight/ 3.85;
let particleArray = [];
let mouse = {
x: null,
y: null,
radius: 74
}
window.addEventListener('mousemove',(e)=>{
mouse.x = event.x + canv.clientLeft/2;
mouse.y = event.y + canv.clientTop/1.2;
});
function drawImage(){
let imageWidth = png.width; //These to values crop if / sum no.
let imageHeight = png.height;
const data = ctx.getImageData(0, 0, imageWidth, imageHeight); //Gets img data for particles
ctx.clearRect(0,0, canv.width, canv.height); // Clears the original img as its now being stored in the variable data.
class Particle {
constructor(x, y, color, size){
this.x = x + canv.width/2 - png.width * 174, //Chngd Ok:74
this.y = y + canv.height/2 - png.height * 32, //Ch<2 Ok:16
this.color = color,
this.size = 2.28, // Particle Size > Changed this value. from 2 i think!.
this.baseX = x + canv.width/1.8 - png.width * 3.1, //Chngd ok:5.1
this.baseY = y + canv.height/1.2 - png.height * 2.8,
this.density = (Math.random() * 14) + 2;
}
draw() {
ctx.beginPath(); // this creates the sort of force field around the mouse pointer.
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
update() {
ctx.fillStyle = this.color;
// Collision detection
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let forceDirectionX = dx / distance;
let forceDirectionY = dy / distance;
// Max distance, past that the force will be 0
const maxDistance = 144;
let force = (maxDistance - distance) / maxDistance;
if (force < 0) force = 0;
let directionX = (forceDirectionX * force * this.density * 0.6);
let directionY = (forceDirectionY * force * this.density * 8.7); //Ch.this
if (distance < mouse.radius + this.size) {
this.x -= directionX;
this.y -= directionY;
} else {
if (this.x !== this.baseX){
let dx = this.x - this.baseX;
this.x -= dx/54; // Speed Particles return to ori
} if (this.y !== this.baseY){
let dy = this.y - this.baseY;
this.y -= dy/17; // Speed Particles return to ori
}
}
this.draw();
}
}
function init(){
particleArray = [];
for(let y = 0, y2 = data.height; y<y2; y++){
for(let x =0, x2 = data.width; x<x2; x++){
if(data.data[(y * 4 * data.width) + (x*4) + 3] > 128){
let positionX = x + 25;
let positionY = y + 45; // Co-ords on Canv
let color = "rgb(" + data.data[(y * 4 * data.width) + (x * 4)] + "," +
data.data[(y * 4 * data.width) + (x * 4) + 1] + "," +
data.data[(y * 4 * data.width) + (x * 4) + 2] + ")";
particleArray.push(new Particle(positionX * 2, positionY * 2, color));
} /* These number effect png size but its to high */
}
}
}
function animate(){
requestAnimationFrame(animate);
ctx.fillStyle = 'rgba(0,0,0,.07)';
ctx.fillRect(0,0, innerWidth, innerHeight);
for(let i =0; i < particleArray.length; i++){
particleArray[i].update();
}
}
init();
animate();
}
const png = new Image();
png.src = "RemovedBase64StringToBig";
window.addEventListener('load',(e)=>{
console.log('page has loaded');
ctx.drawImage(png, 0, 0);
drawImage();
})
});
have managed to shorten it by about 100 characters by shortening all the variable names > PartArr, ImgWidth, DirX, DirY etc, but apart from minifying it is there any other ways to optimize this? and fix the full screen issue?
I tried to add it to a JSfiddle, So I could link to it here, but I don't think this is allowing the base64 string, its not loading anything anyway. The canvas loads, with the bg just no image or animation.
I've found out what part of the problem is with full screen, the cursor position is actually about 300px to the right of where the actual cursor is, but I still have no idea how to fix this or fix the major lagging performance issues. Guessing its alot to compute even just with 100x100.
One option I can think of to make this perform better would be to move it & its calculations, into its own dedicated web worker & convert the image to Webp but i'm still not very clued up about web workers or how to implement them properly.. Will play around & see what I can put together using All the suggestions in the comments & answers.
I'm adding these links only for future reference, when I come back to this later on:
MDN Canvas Optimizations
Html5Rocks Canvas Performance
Stack Question. Canv ~ Opti
Creating A blob From A Base 64 String in Js
Secondary bonus Question,
is there a maximum file size or max px dimensions,
that can be base64 encoded? only asking this as someone on facebook has recently sent me a question regarding another project with multiple base64 encoded images and I was unsure of the answer..
Shortening your code doesn't help much with performance. I'm using Firefox. To check what's taking your time up the most during browser runs in Firefox, you can read Performance from MDN.
The problem with your solution is your fps is dropping hard. This happens because you are painting each Particle every frame. Imagine how laggy it will be when there are thousands of Particles that you need to paint every frame. This paint call is called from your function Particle.draw (which calls the following: ctx.beginPath, ctx.arc, and ctx.closePath). This function, as said, will be called because of Particle.update for each frame. This is an extremely expensive operation. To improve your fps drastically, you can try to not draw each Particle individually, but rather gather all the Particles' ImageData wholly then placing it in the canvas only once in rAQ (thus only one paint happens). This ImageData is an object that contains the rgba for each pixel on canvas.
In my solution below, I did the following:
For each Particle that is dirty (has been updated), modify the ImageData that is to be put in the canvas
Then, after the whole ImageData has been constructed for one frame, only draw once to the canvas using putImageData. This saves a lot of the time needed to call your function Particle.update to draw each Particle individually.
One other obvious solution is to increase the size of Particles so that there are fewer Particles' pixels that are needed to be processed (to alter ImageData). I've also tweaked the code a little so that the image will always be at least 100px high; you can tweak the maths so that the image will always maintain your aspect ratio and respond to window size.
Here's a working example:
const canvas = document.querySelector('#canvas1')
const ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
let canvasWidth = canvas.width
let canvasHeight = canvas.height
let particleArray = []
let imageData = []
// mouse
let mouse = {
x: null,
y: null,
radius: 40
}
window.addEventListener('mousemove', e => {
mouse.x = event.x
mouse.y = event.y
})
function drawImage(width, height) {
let imageWidth = width
let imageHeight = height
const data = ctx.getImageData(0, 0, imageWidth, imageHeight)
class Particle {
constructor(x, y, color, size = 2) {
this.x = Math.round(x + canvas.width / 2 - imageWidth * 2)
this.y = Math.round(y + canvas.height / 2 - imageHeight * 2)
this.color = color
this.size = size
// Records base and previous positions to repaint the canvas to its original background color
this.baseX = Math.round(x + canvas.width / 2 - imageWidth * 2)
this.baseY = Math.round(y + canvas.height / 2 - imageHeight * 2)
this.previousX = null
this.previousY = null
this.density = (Math.random() * 100) + 2
}
stringifyColor() {
return `rgba(${this.color.r}, ${this.color.g}, ${this.color.b}, ${this.color.a}`
}
update() {
ctx.fillStyle = this.stringifyColor()
// collision detection
let dx = mouse.x - this.x
let dy = mouse.y - this.y
let distance = Math.sqrt(dx * dx + dy * dy)
let forceDirectionX = dx / distance
let forceDirectionY = dy / distance
// max distance, past that the force will be 0
const maxDistance = 100
let force = (maxDistance - distance) / maxDistance
if (force < 0) force = 0
let directionX = (forceDirectionX * force * this.density)
let directionY = (forceDirectionY * force * this.density)
this.previousX = this.x
this.previousY = this.y
if (distance < mouse.radius + this.size) {
this.x -= directionX
this.y -= directionY
} else {
// Rounded to one decimal number to as x and y cannot be the same (whole decimal-less integer)
// as baseX and baseY by decreasing using a random number / 20
if (Math.round(this.x) !== this.baseX) {
let dx = this.x - this.baseX
this.x -= dx / 20
}
if (Math.round(this.y) !== this.baseY) {
let dy = this.y - this.baseY
this.y -= dy / 20
}
}
}
}
function createParticle(x, y, size) {
if (data.data[(y * 4 * data.width) + (x * 4) + 3] > 128) {
let positionX = x
let positionY = y
let offset = (y * 4 * data.width) + (x * 4)
let color = {
r: data.data[offset],
g: data.data[offset + 1],
b: data.data[offset + 2],
a: data.data[offset + 3]
}
return new Particle(positionX * 4, positionY * 4, color, size)
}
}
// Instead of drawing each Particle one by one, construct an ImageData that can be
// painted into the canvas at once using putImageData()
function updateImageDataWith(particle) {
let x = particle.x
let y = particle.y
let prevX = particle.previousX
let prevY = particle.previousY
let size = particle.size
if (prevX || prevY) {
let prevMinY = Math.round(prevY - size)
let prevMaxY = Math.round(prevY + size)
let prevMinX = Math.round(prevX - size)
let prevMaxX = Math.round(prevX + size)
for (let y = prevMinY; y < prevMaxY; y++){
for (let x = prevMinX; x < prevMaxX; x++) {
if (y < 0 || y > canvasHeight) continue
else if (x < 0 || x > canvasWidth) continue
else {
let offset = y * 4 * canvasWidth + x * 4
imageData.data[offset] = 255
imageData.data[offset + 1] = 255
imageData.data[offset + 2] = 255
imageData.data[offset + 3] = 255
}
}
}
}
let minY = Math.round(y - size)
let maxY = Math.round(y + size)
let minX = Math.round(x - size)
let maxX = Math.round(x + size)
for (let y = minY; y < maxY; y++){
for (let x = minX; x < maxX; x++) {
if (y < 0 || y > canvasHeight) continue
else if (x < 0 || x > canvasWidth) continue
else {
let offset = y * 4 * canvasWidth + x * 4
imageData.data[offset] = particle.color.r
imageData.data[offset + 1] = particle.color.g
imageData.data[offset + 2] = particle.color.b
imageData.data[offset + 3] = particle.color.a
}
}
}
}
function init() {
particleArray = []
imageData = ctx.createImageData(canvasWidth, canvasHeight)
// Initializing imageData to a blank white "page"
for (let data = 1; data <= canvasWidth * canvasHeight * 4; data++) {
imageData.data[data - 1] = data % 4 === 0 ? 255 : 255
}
const size = 2 // Min size is 2
const step = Math.floor(size / 2)
for (let y = 0, y2 = data.height; y < y2; y += step) {
for (let x = 0, x2 = data.width; x < x2; x += step) {
// If particle's alpha value is too low, don't record it
if (data.data[(y * 4 * data.width) + (x * 4) + 3] > 128) {
let newParticle = createParticle(x, y, size)
particleArray.push(newParticle)
updateImageDataWith(newParticle)
}
}
}
}
function animate() {
requestAnimationFrame(animate)
for (let i = 0; i < particleArray.length; i++) {
let imageDataCanUpdateKey = `${Math.round(particleArray[i].x)}${Math.round(particleArray[i].y)}`
particleArray[i].update()
updateImageDataWith(particleArray[i])
}
ctx.putImageData(imageData, 0, 0)
}
init()
animate()
window.addEventListener('resize', e => {
canvas.width = innerWidth
canvas.height = innerHeight
canvasWidth = canvas.width
canvasHeight = canvas.height
init()
})
}
const png = new Image()
png.src = " data:image/png;base64,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"
window.addEventListener('load', e => {
// Ensuring height of image is always 100px
let pngWidth = png.width
let pngHeight = png.height
let divisor = pngHeight / 100
let finalWidth = pngWidth / divisor
let finalHeight = pngHeight / divisor
ctx.drawImage(png, 0, 0, finalWidth, finalHeight)
drawImage(finalWidth, finalHeight)
})
#canvas1 {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
<canvas id="canvas1"></canvas>
UPDATE 2: I have managed to optimize further. Now it can render FullHD image (1920x1080) without downgrading quality (on my PC it runs at about 20fps).
Take a look this code on JSFiddle (you can also tweak values).
Thanks also goes to #Richard (check out his answer) for idea to put all data in ImageData and make a single draw call. Code on JSFiddle is combination of his and mine optimizations (code below is my old code).
EDIT: Updated JSFiddle link, optimized more by spreading work of stationary particles across multiple frames (for given settings it improves performance for about 10%).
Regarding optimization, you won't achieve much by minifying code (in this case) because code that eats up CPU is runtime intensive (executes each frame). Minification is good for optimizing loading, not runtime execution.
Most of time is spent on drawing, and after some investigation I have found few performance optimizations but these are not enough to make big difference (eg. ctx.closePath() can be omitted and this saves some milliseconds).
What you can do is to either reduce resolution of image or skip some pixels in image in order to reduce work.
Additionally you could spread work across multiple frames to improve frame rate (but keep in mind if you spread it on more than few frames you might start seeing flickering).
Fullscreen issue can be solved by simply re-initializing everything on resize event.
Below is code with mentioned optimizations and fullscreen fix. Sample image is 375x375 pixels.
UPDATE: I played a little with code and I managed to improve further performance by optimizing calls (things I mentioned below code snippet). Code is updated with these changes.
var canv
var ctx
//performance critical parameters
const pixelStep = 2 //default 1; increase for images of higher resolution
const maxParticlesToProcessInOneFrame = 20000
//additional performance oriented paramteres
// Max distance, past that the force will be 0
const maxDistance = 144
const mouseRadius = 74
//customization parameters
const ctxFillStyle = 'rgba(0,0,0,.07)'
const speedOfActivatingParticle = 1
const speedOfRestoringParticle = 0.1
const png = new Image();
const mouse = {
x: null,
y: null
}
window.addEventListener('mousemove', (e) => {
mouse.x = event.x + canv.clientLeft;
mouse.y = event.y + canv.clientTop;
})
class Particle {
constructor(x, y, size) {
this.x = x
this.y = y
this.size = pixelStep
this.baseX = x
this.baseY = y
this.density = (Math.random() * 14) + 2
}
draw() {
//ctx.beginPath(); // this creates the sort of force field around the mouse pointer.
//ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.rect(this.x, this.y, this.size * 2, this.size * 2)
//ctx.closePath();
}
update() {
// Collision detection
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < mouseRadius + this.size) {
let forceDirectionX = dx / distance;
let forceDirectionY = dy / distance;
let force = (maxDistance - distance) / maxDistance;
if (force < 0)
force = 0;
const forceTimesDensity = force * this.density * speedOfActivatingParticle
let directionX = (forceDirectionX * forceTimesDensity);
let directionY = (forceDirectionY * forceTimesDensity); //Ch.this
this.x -= directionX;
this.y -= directionY;
} else {
if (this.x !== this.baseX) {
let dx = this.x - this.baseX;
this.x -= dx * speedOfRestoringParticle; // Speed Particles return to ori
}
if (this.y !== this.baseY) {
let dy = this.y - this.baseY;
this.y -= dy * speedOfRestoringParticle; // Speed Particles return to ori
}
}
this.draw();
}
}
window.addEventListener('resize', initializeCanvas)
window.addEventListener("load", initializeCanvas, {
once: true
})
let animationFrame = null
function initializeCanvas(e) {
cancelAnimationFrame(animationFrame)
canv = document.getElementById('Canv');
ctx = canv.getContext('2d');
canv.width = window.innerWidth;
canv.height = window.innerHeight;
let particles = {}
function drawImage() {
let imageWidth = png.width; //These to values crop if / sum no.
let imageHeight = png.height;
const data = ctx.getImageData(0, 0, imageWidth, imageHeight); //Gets img data for particles
ctx.clearRect(0, 0, canv.width, canv.height); // Clears the original img as its now being stored in the variable data.
function init() {
particles = {}
for (let y = 0, y2 = data.height; y < y2; y += pixelStep) {
for (let x = 0, x2 = data.width; x < x2; x += pixelStep) {
if (data.data[(y * 4 * data.width) + (x * 4) + 3] > 128) {
let positionX = x
let positionY = y
let color = "rgb(" + data.data[(y * 4 * data.width) + (x * 4)] + "," +
data.data[(y * 4 * data.width) + (x * 4) + 1] + "," +
data.data[(y * 4 * data.width) + (x * 4) + 2] + ")";
let particlesArray = particles[color]
if (!particlesArray)
particlesArray = particles[color] = []
particlesArray.push(new Particle(positionX * 2, positionY * 2))
} /* These number effect png size but its to high */
}
}
}
let particlesProcessed = 0
let animateGenerator = animate()
function* animate() {
particlesProcessed = 0
ctx.fillStyle = ctxFillStyle;
ctx.fillRect(0, 0, innerWidth, innerHeight);
let colors = Object.keys(particles)
for (let j = 0; j < colors.length; j++) {
let color = colors[j]
ctx.fillStyle = color
let particlesArray = particles[color]
ctx.beginPath()
for (let i = 0; i < particlesArray.length; i++) {
particlesArray[i].update()
if (++particlesProcessed > maxParticlesToProcessInOneFrame) {
particlesProcessed = 0
ctx.fill()
yield
ctx.beginPath()
}
}
ctx.fill()
}
}
init();
function animateFrame() {
animationFrame = requestAnimationFrame(() => {
if (animateGenerator.next().done) {
animateGenerator = animate()
}
animateFrame()
})
}
animateFrame()
}
console.log('page has loaded');
ctx.drawImage(png, 0, 0, png.width, png.height);
drawImage();
}
png.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAXcAAAF3CAMAAABpHvvMAAAC1lBMVEUAAAAAAAAAAADp6ekAAAAAAACcnJwAAAAAAACgoKAAAAAAAAAAAAAAAADq6uoAAAAAAADu7u6Ojo7d3d0AAABISEj5+fmYmJi2traampr6+vqXl5ebm5u3t7dNTU1HR0eRkZG8vLzd3d3f39+zs7NTU1Pw8PDk5OTu7u7////VuTP54C3r6uoAAADVuDDr6+vYvkL38db54Cv12y7r6+3avjL8/PzXuzPw1i/54Cr///3u7e3Wuzn99sP54TTWujcyMjLl3bf43y354TLk27H//vnZwEnWujXh4eH99b3VuTT875YlJSX//vv19fXXvD0GBgZHR0fawU39/Pfr6ufZv0X+/PT7+OnYvDL69uTdx1v8+e7cxVXYvUDy2S78+vH489tbW1vq6OPhzWz64zzu1C9DQ0PXvDvq59yxsbGFhYXfyWHm377cyW7gy2fbw1D65EP//fD+/Ory6b7x57jg0Ij54jcKCgr09PT59N/v5K/t4abezHfZwlTpzzD39/f+++T07Mfp2ZDbxmDr0S/65UkfHx/q6ODfzn/j0HXaxFr65VDgxTHfwzHn4snk2avq3Jnh1Jbiz3HixzH54S/o5NHz68Lh0pD874387oXk0Xn763VsbGzbx2dPT0/cwDL//vbp5dX17864uLjr3p3l1IL87H376m376GD651vnzDD5+fn++t7b29v++dT28dT28NHn4MTs36Hp25Tl033o48717cv+98f99bfw5bP98qjn2Iv76Wf65lQ8PDzdwjL++tju4qr98qHXuzoVFRXn5+fp5tn9867j2Kb88Zzn1ojm1YV9fX12dnbz8/P99LPi1qDlyzAQEBDX19f++M/Nzc3++MvExMTi1ZwrKysaGhrw8PDT09OsrKxWVlbkyTGXl5eQkJD54TO1tbXi16NkZGSnp6eKioqCgoLlyjGdnZ3j4+OkpKRKSkrkyjCpqank1pD353rMpemXAAAAKXRSTlMABAvSFCRoCA5mIh8RFtEnG95Wtxg39GuHavRsaYU1OVSCuLZyMd3D3S9YQ4oAABlBSURBVHja7NQBzoMgDIZh8UcpKzoEtyUmIxCnife/4F+9BAb63KBvvrRhjDHGGGOM1UoUq7knUYnmRsRFKdUWjQ68U3tBqDiA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";
body {
margin: 0;
padding: 0;
}
#Canv {
width: 100vw;
height: 100vh;
filter: blur(1.5px);
}
<canvas id="Canv"></canvas>
If you still need to optimize, you could do some optimization regarding ctx.beginPath(), ctx.fill() and ctx.rect() calls. For example, try to combine sibling pixels (pixels that are next to each other) and render them all in one call. Furthermore, you could merge similar colors in single color, but downside is that image will loose quality (depending on how much colors are merged).
Also (if this is option) you might want to set fixed canvas size rather than dynamically sized.
Disclosure: On my PC given code works nicely, but on others it might still have performance issues. For that reason try to play with pixelStep and maxParticlesToProcessInOneFrame variable values.

Why is the bullet going the wrong way?

I'm trying to make a shooter game and the bullets are going the wrong way.
I've tried to mess a little bit with the formulas but I have no clue on how to solve this issue.
setInterval(function(){
$(".bullet").each(function(i,bullet) {
var x = parseFloat($(bullet).css("left")) + parseFloat($(bullet).attr("y"));
var y = parseFloat($(bullet).css("top")) + parseFloat($(bullet).attr("x"));
$(bullet).css({"top":y+"px","left":x+"px"});
});
},20);
$(window).click(function(e) {
var dx = (e.pageX - (x+37.5));
var dy = (e.pageY - (y+37.5));
var mag = Math.sqrt(dx * dx + dy * dy);
var velX = (dx / mag) * 15;
var velY = (dy / mag) * 15;
$("#game").append("<div class=\"bullet\" x=\""+velX+"\" y=\""+velY+"\" age=\"0\" style=\"top:"+(parseFloat($("#user").css("top"))+37.5)+"px;left:"+(parseFloat($("#user").css("left"))+37.5)+"px;\"></div>");
});
Here's a GIF of what's happening
You are adding X to Y and Y to X is one observation:
var x = parseFloat($(bullet).css("left")) + parseFloat($(bullet).attr("x"));
var y = parseFloat($(bullet).css("top")) + parseFloat($(bullet).attr("y"));
instead of
var x = parseFloat($(bullet).css("left")) + parseFloat($(bullet).attr("y"));
var y = parseFloat($(bullet).css("top")) + parseFloat($(bullet).attr("x"));

Mouse coordinate based perspective rotation with centered div [Javascript]

Second question I've asked, so if I need to add anything else, let me know please!
Been looking everywhere to try and understand concepts, but I feel like I am over thinking it. What I am trying to do is base "transform: rotateX("x"deg)" off of the x location of the users cursor proportionately from the size of the window. Here is a gif that does what I want, but they use matrix3D instead of just transform's rotate.
http://i.imgur.com/tUZSLXA.gif
Here is a js fiddle of what I have so far.
https://jsfiddle.net/z7bet2sw/
JS:
function rotate() {
var x = event.clientX;
var w = window.innerWidth;
var midpoint = w / 2;
// restrictions for rotation
if (x > -20 && x < 20) {
document.getElementsById("logo").style.transform = "perspective(550px)" + "rotateY(" + x + "deg)";
} else {
}
}
document.addEventListener("mousemove", rotate);
Thanks!
You need to pass the event parameter to rotate function from addEventListener, and also you need to calculate a relative position from the x coordinates to get a value between -20 and 20:
function rotate (event)
{
var x = event.clientX;
var w = window.innerWidth;
var midpoint = w / 2;
var pos = x - midpoint;
var val = (pos / midpoint) * 20;
var logo = document.getElementById ("logo");
logo.style.transform = "perspective(550px) rotateY(" + val + "deg)";
}
document.addEventListener("mousemove", function(event)
{
rotate(event)
}, false);
JSFiddle
Two issues:
you are using document.getElementsById which should actually be document.getElementById (no s)
you should subtract midpoint from x
Updated code:
function rotate() {
var x = event.clientX;
var w = window.innerWidth;
var midpoint = w / 2;
x -= midpoint;
if (x > -20 && x < 20) {
document.getElementById("logo").style.transform = "perspective(550px)" + "rotateY(" + x + "deg)";
} else {
}
}
fiddle: https://jsfiddle.net/jacquesc/z7bet2sw/1/

Rotation around a specific point

I'm trying to rotate an element around another element. I've put this code together, and even though the code works fine, I have a little problem with the element's rotation. It's not very accurate. The blue dot leaves the orbit when at the bottom.
I've played with the code but couldn't fix it. You can check the fiddle example.
rotateObj function taken from Felix Eve's answer.
function markCircleCenter(circ, orig) {
if (typeof circ == "string") {
var elm = document.getElementById(circ);
} else { var circle = circ; }
if (typeof orig == "string") {
var mark = document.getElementById(orig);
} else { var elm = orig; }
var width = parseInt(window.getComputedStyle(elm).width, 10);
var height = parseInt(window.getComputedStyle(elm).height, 10);
var top = parseInt(window.getComputedStyle(elm).top, 10);
var left = parseInt(window.getComputedStyle(elm).left, 10);
var markWidth = parseInt(window.getComputedStyle(mark).width, 10);
var markHeight = parseInt(window.getComputedStyle(mark).height, 10);
var circle = {
circle: elm,
x: width/2 + left,
y: height/2 + top,
};
mark.style.position = "absolute";
mark.style.top = circle.y - markWidth/2 + "px";
mark.style.left = circle.x - markHeight/2 + "px";
document.body.insertBefore(mark, elm.nextSibling);
return circle;
}
function placeObjIntoOrbit(obj) {
if (typeof obj == "string") {
var obj = document.getElementById(obj);
} else { var obj = obj; }
var objPos = {
obj: obj,
x: origin.x,
y: parseInt(window.getComputedStyle(origin.circle).top, 10) - parseInt(window.getComputedStyle(obj).width, 10) / 2,
};
obj.style.position = "absolute";
obj.style.top = objPos.y + "px";
obj.style.left = objPos.x + "px";
return objPos;
}
function rotateObj(pointX, pointY, originX, originY, angle) {
var angle = angle * Math.PI / 180.0;
return {
x: Math.cos(angle) * (pointX-originX) - Math.sin(angle) * (pointY-originY) + originX,
y: Math.sin(angle) * (pointX-originX) + Math.cos(angle) * (pointY-originY) + originY,
};
}
var angle = 0;
var origin = markCircleCenter("circle", "origin");
var point = placeObjIntoOrbit("point");
function spin() {
if (angle >= 360) { angle = 0; }
angle += 2;
var newCoor = rotateObj(point.x, point.y, origin.x, origin.y, angle);
point.obj.style.left = newCoor.x + "px";
point.obj.style.top = newCoor.y + "px";
}
setInterval("spin('point')", 1000/30);
You are computing the center of the point but positioning it using its top-left point. You need to take the dimension of the point into account when you position it. Try changing the last two lines in function spin() to:
point.obj.style.left = (newCoor.x - 4) + "px";
point.obj.style.top = (newCoor.y - 4) + "px";
(You should get rid of the "- 4" and substitute half the width and height of the point, but this gets the idea across more tersely.)

Conflicting JavaScript for gyroscope move and touchmove on iOS, how do I pause one part when the second is in use?

I'm punching above my weight a bit with some JavaScript code on an iOS web app.
What I'm doing is moving a layer above another layer using:
Gyro and accelerometer data and
Touch input.
This means I've got two bits of javascript both trying to move the same thing. This doesn't work so well, as you can see from the current version when viewed on an iOS device (hint: the transparency is turned on with the button on the right, and no, it can't be moved with a mouse right now and wont move unless you are using an iOS device with a Gyro).
So I've done the hard bits but I'm stuck on what I expect is a gotcha based on my newb(ish) level of proficiency with JavaScript.
How can I stop the gyro-move code when the touch-move code is active? Also I guess I'll need to update the x+y values so the transparency does not jump position once the touch-move ends.
I've tried adding an if, else statement to the top of the code but this seems to break the whole lot.
BTW thanks to everyone on StackOverflow, previous Q and As have been tons of help in getting me this far.
Help will be most appreciated.
Stefan
Here's my code so far, it's living in the body element...
var x = 0, y = 0,
vx = 0, vy = 0,
ax = 0, ay = 0;
var transp = document.getElementById("transp");
if (window.DeviceMotionEvent != undefined) {
window.ondevicemotion = function(e) {
ax = event.accelerationIncludingGravity.x * 5;
ay = event.accelerationIncludingGravity.y * 5 + 19;
document.getElementById("accelerationX").innerHTML = e.accelerationIncludingGravity.x;
document.getElementById("accelerationY").innerHTML = e.accelerationIncludingGravity.y;
document.getElementById("accelerationZ").innerHTML = e.accelerationIncludingGravity.z;
if ( e.rotationRate ) {
document.getElementById("rotationAlpha").innerHTML = e.rotationRate.alpha;
document.getElementById("rotationBeta").innerHTML = e.rotationRate.beta;
document.getElementById("rotationGamma").innerHTML = e.rotationRate.gamma;
}
}
setInterval( function() {
var landscapeOrientation = window.innerWidth/window.innerHeight > 1;
if ( landscapeOrientation) {
vx = vx + ay;
vy = vy + ax;
} else {
vy = vy - ay;
vx = vx + ax;
}
vx = vx * 0.98;
vy = vy * 0.98;
y = parseInt(y + vy / 70);
x = parseInt(x + vx / 70);
boundingBoxCheck();
transp.style.top = y + "px";
transp.style.left = x + "px";
}, 25);
}
function boundingBoxCheck(){
if (x<-310) { x = -310; vx = -vx; }
if (y<-300) { y = -300; vy = -vy; }
if (x>document.documentElement.clientWidth) { x = document.documentElement.clientWidth; vx = -vx; }
if (y>document.documentElement.clientHeight+400) { y = document.documentElement.clientHeight+400; vy = -vy; }
}
$.fn.moveable = function() {
var offset = null;
var start = function(e) {
var orig = e.originalEvent;
var pos = $(this).position();
offset = {
x: orig.changedTouches[0].pageX - pos.left,
y: orig.changedTouches[0].pageY - pos.top
};
};
var moveMe = function(e) {
e.preventDefault();
var orig = e.originalEvent;
$(this).css({
top: orig.changedTouches[0].pageY - offset.y,
left: orig.changedTouches[0].pageX - offset.x
});
};
this.bind("touchstart", start);
this.bind("touchmove", moveMe);
};
$(".moveable").moveable();
My first thought would be is to have an isTouching variable that gets set to true on touchstart and set back to false on touchEnd. Then using a conditional the gyro code will only run if the isTouching variable is false. Hope that helps.

Categories

Resources