Handle back/forward button click in browser - javascript

I want to console.log(1) if the user clicks on the forward button inside browser,
-1 if the user clicks on back button
0 if user reloads the page,
This is my code so far, I can not achieve to get -1, why am I doing wrong??
My approach is setting a state inside history object in the component and compare it vs the lenght of the history object...
async function handleBackForwardButton() {
// If none, then zero
const historyLength = Number(sessionStorage.getItem("historyLength"));
let { state } = await window.history.state;
let position;
if (state === null || state === undefined) {
// New entry on the stack
position = historyLength + 1;
debugger;
// Stamp the entry with its own position in the stack
window.history.replaceState(historyLength, /*no title*/ "");
// (2) Keep track of the last position shown
sessionStorage.setItem("historyLength", String(window.history.length));
debugger;
// (3) Discover the direction of travel by comparing the two
const direction = Math.sign(position - historyLength);
console.log("Forward should be (1) :" + direction);
debugger;
// forward should be (1)
} else if (historyLength > state) {
const direction = Math.sign(state - historyLength);
console.log("Backward should be (-1) :" + direction);
debugger;
//One of backward (-1)
} else if (historyLength === state) {
const direction = Math.sign(state - historyLength);
console.log("Reloading page should be (0) : " + direction);
debugger;
//Reloading page should be (0)
}
}
window.addEventListener("pageshow", handleBackForwardButton);
window.addEventListener("popstate", handleBackForwardButton);

Related

State seems to be reverting back to its initial state after useeffect is run

I am having an issue where it seems a state is reverting to its initial state without being prompted to. The state is change in a function, which then triggers a useeffect. The useffect then runs, recognizing and console.logging the update state. Then however, the next call to the function says the state is still in its initial state. My code is below, unfortunately I cant include a full snippet as it includes some things that cannot be shared/run.
Here is where I declare my states:
const [playerTeam, setPlayerTeam] = useState({});
const [movement, setMovement] = useState({ active: false, character: null });
Here is my useeffect:
useEffect(() => {
const canvas = canvasReference.current;
const context = canvas.getContext("2d");
console.log("drawing", movement);
setCharacterUpdate(false);
mapImage.onload = () => {
canvas.width = mapImage.width;
canvas.height = mapImage.height;
context.drawImage(mapImage, 0, 0);
for (const [key, value] of Object.entries(playerTeam)) {
context.drawImage(
playerTeam[key].spriteImg,
playerTeam[key].x,
playerTeam[key].y
);
}
};
}, [playerTeam, movement]);
Here is where I change states that I expect to trigger the useeffect:
function handleClick(canvas, e) {
console.log(movement);
//get the coordinates of the user's curson on click
let rect = canvas.getBoundingClientRect();
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
//movement mode is active (last click was a click on an ally character)
if (movement.active) {
moveCharacter(x, y);
}
//if we arent in movement mode...
else {
//cycle through player's team
for (const [key, value] of Object.entries(playerTeam)) {
//if the coordinates of the click are on the same 48 x 48 tile as one of the ally characters
if (
x >= playerTeam[key].x &&
x <= playerTeam[key].x + 47 &&
y >= playerTeam[key].y &&
y <= playerTeam[key].y + 47
) {
console.log("Batman!");
const enterMovement = {
active: true,
character: playerTeam[key].name,
};
//put us in movement mode
setMovement(enterMovement);
break;
}
}
}
}
//function for moving the character
function moveCharacter(x, y) {
console.log("here");
let newTeam = playerTeam;
newTeam[movement.character].x = x - (x % 48);
newTeam[movement.character].y = y - (y % 48);
setPlayerTeam(newTeam);
let exitMovement = { active: false, character: null };
setMovement(exitMovement);
console.log(movement, playerTeam["batman"]);
}
Here are some things I noticed that confuse me:
The handleClick function never reads movement.active as true. The console log at the beginning of handle click shows it is false on every call to the function.
The useeffect function, however, has a console log in it that shows that the movement field is, in fact, being changed to true. The useeffect runs on each click, indicating that the movement field is in fact changing, but somehow between the useffect triggering and the next click, the movement.active field is reverting back to its initial state.
*The playerTeam state is not empty by the way, it is populated in a separate section of code.
Thanks for any ideas/answers!

Problem updating a state using setInterval in React application

I have a function called getRandomHexagram() which returns a random 6-character string that is used for props in my <hexagram string={stringstate} /> component in my I Ching divination app.
I also have a function called resettimer() which retrieves this random 6-character string multiple times and passes it to my component within a few seconds, so that while setInterval is running, the component will keep changing its appearance until clearInterval is called.
I would like to continuously update the state using resettimer() function run until it ends, and then use the new state in another function together with clearInterval.
What is strange to me is that while the if portion of the timer is running, the component continuously updates as it should. But when I try to call the final state in the } else { statement together with clearInterval, console always shows that stringstate is still "VVVVVV", or whatever was the last prop that my component used in my previous click, NOT what my component is currently using.
function getRandomHexagram() {
const flipcoins = () => {
return (
(Math.floor(Math.random() * 2) + 2) +
(Math.floor(Math.random() * 2) + 2) +
(Math.floor(Math.random() * 2) + 2)
);
};
const drawline = (coinvalue) => {
let line;
if (coinvalue == 6) {line = "B";}
if (coinvalue == 7) {line = "V";}
if (coinvalue == 8) {line = "P";}
if (coinvalue == 9) {line = "W";}
return line;
};
return (
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins())
);
}
function App() {
const [stringstate, setStringstate] = useState("VVVVVV");
function resettimer() {
var timesrun = 0;
var randomtime = Math.floor(Math.random() * 15) + 10;
const interval = setInterval(() => {
timesrun += 1;
if (timesrun < randomtime) {
thisString = getRandomHexagram();
setStringstate(thisString);
console.log("the current permutation is: " + thisString);
// console and component shows this correctly.
} else {
clearInterval(interval);
console.log("the final state state is: " + stringstate);
// Call another function here using the NEW stringstate
// But console shows that stringstate is still showing
// the old state of VVVVVV despite my component showing otherwise
}
}, 100);
}
... ...
return (
<div className="App">
<Button onClick={() => resettimer()}>
<Hexagram string={stringstate} />
</div>
);
}
When the button is clicked and the function invoked, my console shows this:
the current permutation is: VPPVPB
the current permutation is: VPPPVV
the current permutation is: BPBVBV
the current permutation is: VVPVPV
the current permutation is: PPBVPV
the current permutation is: PPPBBB
the current permutation is: VVPPPV
the final state state is: VVVVVV
Any idea why this is happening? Any advice is much appreciated.

Restore read position in dynamic content on next visit

I have a big chapter of a book that my user reads. Chapters can have any font-size; the window can be any size. How can I save the exact position in text (top most of currently visible window) in a database, so when user continues to read this chapter from any device, I can scroll him there?
I was trying with window.pageYOffset, then percentage of scroll position, but it's not accurate with dynamic window and font-size, and can only work in same environment (font-size, windows size).
My only idea now is to split the chapter into lines:
const lines = (chapter.match(/\r?\n/g) || '').length + 1
And then somehow find a line that is top most of the currently visible window, save its number, and scroll to it on load.
As an alternative, maybe something that can give me the currently top most visible html element so I can stick to it.
Any ideas?
Update: now I'm trying to get all the elements in the parent of the content div and iterate through them getting the element position in the viewport with getBoundingClientRect(). I have hopes for it.
First you get array of paragraphs
let children = Array.from(contentRef.current.children)
Them do binary search of the closest to zero (top of the visible screen) paragraph.
let closestIndex = binaryClosest(
children,
target => 0 - target.getBoundingClientRect().y
)
localStorage.setItem('index', closestIndex)
function binaryClosest(a, compare) {
let i = binarySearch()
if (i === 0) return 0
if (i === a.length) return i - 1
let d1 = -compare(a[i]),
d2 = compare(a[i - 1])
return d1 < d2 ? i : i - 1
function binarySearch() {
let le = 0,
ri = a.length - 1
while (le <= ri) {
let mid = (le + ri) >> 1,
cmp = compare(a[mid])
if (cmp > 0) {
le = mid + 1
} else if (cmp < 0) {
ri = mid - 1
} else {
return mid
}
}
return le
}
}
Now you can save this closestIndex in database and on next load scroll to it:
let children = Array.from(contentRef.current.children)
window.scrollTo(0, children[localStorage.getItem('index')].offsetTop)

JS Function Calls from HTML + Reactive display

The game is WAR, or Get Your Neighbour, a traditional game utilising a standard deck of 52 cards, no jokers. Currently the code recognises when a card is above 10 and so the rules of the game are being followed, all that is great, I've designed a timer that takes the value of the card 2-14, subtracts 10, then uses that number for the round of turns the other player has to draw above 10 before you win. Still building the cooperative/multiplayer element but for now, I'd just like to get this bloody button working!
When I click it, it does nothing. Before, it would tell me that "'timerf' is not a function". I'm probably doing something very obvious like problems with the order that things are loaded/data is parsed, but I'm still learning so I'd appreciate any help! Any questions, let me know.
var card = null; // setem 160517
var timer = null; //
window.onload = function() {
function draw(min, max) { // draw a card between 2-14
card = document.getElementById("draw").innerHTML = Math.floor(Math.random()*((max - min)+1) + min); // min 2, max 14
if (card > 10) {
timer = card - 10;
timerf(timer);
} else if (card < 11 && timer > 0) {
timer = timerf(timer-1);
}
} // draw
//draw(2,14);
document.getElementById("clickMe").onclick = draw(2,14);
} // window.onload
function timerf(timer) { // print turns to win
if (timer > 0 && timer < 5 && timer != 1) { // print turns to win
console.log("you have " + timer + " turns to win!");
} else if (timer == 1) {
console.log("you have " + timer + " turn to win!");
}
}
<div id="draw"></div>
<button id="clickMe">WAR!</button>
The return value of the draw function is undefined because it has no return statement.
document.getElementById("clickMe").onclick = draw(2,14);
… so you are assigning undefined to the onclick property.
You have to assign the function you want to call.

What is wrong with this code? Am I going about this the right way?

Okay, so I'm fairly new to programming. I've been learning to code for quite sometime now, but I hadn't really MADE anything. That considered, I'm attempting to make my first project using JavaScript and make a snake game. Unfortunately, I've ran into multiple problems, which obviously is something that comes hand in hand with programming, but I am new and I'm stuck. Can someone help me figure out if I'm coding this in an efficient way. Also, I have a more specific issue. I've added basic movement functionality to the head of my snake, but I can't figure out how to get the rest of it's parts to follow. If someone could explain to me how to do this, that would be incredible. I've worked about two weeks now to try and figure it out and I'm just stumped. I'm using Raphael's JavaScript Library to generate the graphics on an SVG canvas.
/*
Libraries in use:
1. Rapheal
2. jQuery
*/
// This variable is set to an array so that we can add multiple snakeParts to our PrimarySnake.
var snakeParts = [],
// This variable uses Raphael to generate a canvas.
snakeCanvas = Raphael(10, 10, 400, 400),
// This generates a rectangle that fills the canvas.
snakeCanvasBg = snakeCanvas.rect(0,0,400,400),
// This variable is set to an array so that we can use each and every direction that is pressed.
direction = [],
// This variable is set to an array so that we can use the turn coordinates of our first snake part.
turnCoords = [];
// Generates and returns a random number between 0 and 400. This function is used to help generate the goal of our snake at a random location on the canvas.
function getRandNum () {
var rand = Math.round(Math.random()*400);
// This while loop ensures that our snakeGoal never exceeds the coordinates x = 390 or y = 390. If it did, it's parts would be cut from the canvas.
while (rand > 395) {
rand = Math.round(Math.random()*400);
}
// This while loop ensures that our rand variabe will always be divisible by 10, which is used to make sure our snakeGoal and snakePart elements are always rendered in coordinates divisible by 10.
while (rand % 10 !== 0) {
var randString = rand.toString(),
// This variable stores the whole length of our randString variable.
randStringLength = randString.length,
// This variable stores the last number of our rand as a string character.
subtractionChar = randString.charAt(randStringLength - 1),
// This variable stores the last number of our rand as a integer.
subtractionInt = parseInt(subtractionChar),
// Finally, this line subtracts the last number of our rand from the entirety and then sets that value equal to rand, ensuring that rand is always divisible by 10.
rand = rand - subtractionInt;
}
return rand;
}
// This function is called any time a button is pressed. The jQuery which method allows our code to compare if the key pressed is equal to the keyCode of a designated key.
$(document).keydown(
function (pressedDirection) {
if (pressedDirection.which === 37) {
direction.push("left");
} else if (pressedDirection.which === 38) {
direction.push("up");
} else if (pressedDirection.which === 39) {
direction.push("right");
} else if (pressedDirection.which === 40) {
direction.push("down");
} else if (pressedDirection.which === 32) {
direction.push("stop");
}
if (pressedDirection.which === 37 || pressedDirection.which === 38 || pressedDirection.which === 39 || pressedDirection.which === 40 || pressedDirection.which === 32) {
console.log(direction[direction.length - 1]);
PrimarySnake.addTurnCoords();
PrimarySnake.movePeice();
}
// This prevents our screen from scrolling when an arrow key is
pressedDirection.preventDefault();
}
);
function Snake () {
// This method generates a new peice to the Snake.
this.addPart = function () {
console.log(snakeParts.length);
snakeParts[snakeParts.length] = snakeCanvas.rect(0,0,10,10);
snakeParts[snakeParts.length - 1].attr("fill", "blue");
snakeParts[snakeParts.length - 1].attr("stroke-width", ".25");
}
// This method provides the movement functionality of our Snake.
this.moveDirection = function () {
for (value in snakeParts) {
var currentCoord = [snakeParts[value].attr("x"), snakeParts[value].attr("y")];
// This if-else statement moves the snakePart at the -value- index up, down, left, or right according to the last direction pressed.
if (direction[direction.length - 1] === "up") {
snakeParts[value].attr("y", currentCoord[1] - 10);
} else if (direction[direction.length - 1] === "down") {
snakeParts[value].attr("y", currentCoord[1] + 10);
} else if (direction[direction.length - 1] === "left") {
snakeParts[value].attr("x", currentCoord[0] - 10);
} else if (direction[direction.length - 1] === "right") {
snakeParts[value].attr("x", currentCoord[0] + 10);
}
}
}
this.moveInterval;
// This function makes our moveDirection move our snakePeice every 50 milliseconds.
this.movePeice = function () {
var moveDirection = this.moveDirection;
// clearInterval is used to eliminate any interval previously running, ensuring that our peices only move one direction at a time.
clearInterval(this.moveInterval);
this.moveInterval = setInterval(function(){moveDirection()}, 50);
}
// This function adds an array of coordinates to the turnCoords array.
this.addTurnCoords = function () {
turnCoords.push([snakeParts[0].attr("x"), snakeParts[0].attr("y")]);
}
}
// This generates a new instance of our Snake class.
var PrimarySnake = new Snake();
// This generates a new part on the canvas.
PrimarySnake.addPart();
// This fills our snakeCanvasBg with a grey color, giving us a grey background.
snakeCanvasBg.attr("fill", "#CDCDCD");
Well, your code seems nice, or at least "efficient" as you are calling it.
To make the parts of your snake follow its head, you must iterate through its parts and assign each (n+1) piece the coordinates from (n). To do so, start with the last piece and iterate up to the first one, which movement is defined by the user, like in:
this.moveDirection = function () {
// Move every piece except the head.
for (var i = snakeParts.length - 1; i > 0; i--) {
snakeParts[i].attr("x", snakeParts[i-1].attr("x"));
snakeParts[i].attr("y", snakeParts[i-1].attr("y"));
}
// Now move the head.
if (direction[direction.length - 1] === "up") {
snakeParts[value].attr("y", currentCoord[1] - 10);
} else if (direction[direction.length - 1] === "down") {
snakeParts[value].attr("y", currentCoord[1] + 10);
} else if (direction[direction.length - 1] === "left") {
snakeParts[value].attr("x", currentCoord[0] - 10);
} else if (direction[direction.length - 1] === "right") {
snakeParts[value].attr("x", currentCoord[0] + 10);
}
}
That code may need a bit of work but that's the idea. Hope it helps!

Categories