I have a component ("Mover.svelte") that's responsible for providing mouse-drag functionality to whatever component is in its ("Button.svelte" in this case.)
I'm trying to display the current X/Y position in the child object, which works, but doesn't update reactively as the object moves. (There's no error message, just not working as expected.)
I have tried a variety of ways to do this, including Svelte's context API, but none of the approaches I've tried seem to enable the reactivity.
REPL:
https://svelte.dev/repl/1d2b72dbf8aa465fa60b76f2e93fb0cc?version=3.49.0
app.svelte:
<script>
import Mover from "./Mover.svelte";
import Button from "./Button.svelte";
</script>
<Mover>
<Button />
</Mover>
Mover.svelte:
<script>
import { setContext } from 'svelte';
let moving = false;
export let x = 120;
export let y = 130;
setContext('x', x);
setContext('y', y);
function dragItem(e) {
if (moving) {
x += e.movementX;
y += e.movementY;
}
}
</script>
<div on:mousedown={() => moving = true} on:mouseup={() => moving = false} style="left: {x}px; top: {y}px;">
<slot />
</div>
<svelte:window on:mousemove={dragItem} />
<style>
div {
margin: 0;
padding: 0;
position: absolute;
border: 4px solid green;
}
</style>
Button.svelte:
<script>
import { getContext } from 'svelte';
let x = getContext('x')
let y = getContext('y')
</script>
<button>
X/Y should update on drag:
{x}, {y}
</button>
<style>
button {
border: 4px solid blue;
margin: 0;
padding: 12;
}
</style>
Any help would be greatly appreciated.
If you want to use a context, you have to use a store, as setting/getting the context is a one-time operation. Reactivity does not transfer between files except through props; stores can bridge that gap.
E.g. in Mover:
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
let moving = false;
export let x = 120;
export let y = 130;
const xStore = setContext('x', writable(x));
const yStore = setContext('y', writable(y));
// Synch the stores on change:
$: $xStore = x;
$: $yStore = y;
In Button add $ prefix to read from the stores:
<button>
X/Y should update on drag:
{$x}, {$y}
</button>
REPL
As alternative to contexts, using slot props would also be possible here.
In mover set the props on the slot:
<slot {x} {y} />
Pass them down in App:
<Mover let:x let:y>
<Button {x} {y} />
</Mover>
(Button then needs to declare props for x/y)
REPL
Related
I have a ReactJS code like this
let context = canvas.getContext('2d');
let renderSize = (video1ScaleMode === 'fill') ? fillSize(video1Size, canvasSize, 1) : fitSize(video1Size, canvasSize, 1);
let xOffset = (canvasSize.width - renderSize.width) / 2;
let yOffset = (canvasSize.height - renderSize.height) / 2;
context.drawImage(video1, xOffset, yOffset, renderSize.width, renderSize.height);
This loads the camera inside the canvas. This is working fine.
I want to add a webpage (with animations) behind the camera view. For that I tried to add IFrame (because the URL is from the server)
For that I tried the following code
iframe.js
import React, { Component } from 'react'
import { createPortal } from 'react-dom'
export class IFrame extends Component {
constructor(props) {
super(props)
this.state = {
mountNode: null
}
this.setContentRef = (contentRef) => {
this.setState({
mountNode: contentRef?.contentWindow?.document?.body
})
}
}
render() {
const { children, ...props } = this.props
const { mountNode } = this.state
return (
<iframe
{...props}
ref={this.setContentRef}
>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}
}
My usage is like this
import { IFrame } from './iframe'
const MyComp = () => (
<IFrame>
https://www.helloworld.com
</IFrame>
)
context.drawImage(MyComp, xOffset, yOffset, renderSize.width, renderSize.height);
For that I got error like this
**
Uncaught TypeError: Failed to execute 'drawImage' on
'CanvasRenderingContext2D': The provided value is not of type
'(CSSImageValue or HTMLCanvasElement or HTMLImageElement or
HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement
or VideoFrame)'.
at renderFrame
**
I understand that IFrame component is not like HTMLCanvasElement or ImageElement or VideoElement.
I would like to know how I can add the IFrame or some other way to bring image from URL as background of Video.
You could visually position your iframe behind the canvas using css. Perhaps like this:
<div class="container">
<canvas></canvas>
<iframe></iframe>
</div>
.container {
position: relative;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
How to change the background of the page when it loads without having to click the button?
import img1 from '../images/img1.jpg';
import img2 from '../images/img2.jpg';
import img3 from '../images/img3.jpg';
const [background, setBackground] = useState(img1);
const list = [img1, img2, img3];
const css = {
backgroundImage: `url(${background})`,
};
return (
<div style={css}>
<button onLoad={() => setBackground(list)}>Change background</button>
</div>
);
but setBackground(list)} does not read the array and the onLoad event does not work when the page is reloaded.
thanks for any help!
As mentioned above the solution requires localStorage, useEffect, useRef. All logic is inside useEffect, because when the component is mounting all the magic happens here. working example
Initialize an absolute number with useRef
Trying to get value from localStorage, if is empty then we set
default value. And the same place, we checking the array index.
Second time avoid the check and we increace gotted value andabsolute
number.
Then sending result to state and localStorage
App.js
import { useState, useEffect, useRef } from "react";
export default function App() {
const img1 = "https://dummyimage.com/400x200/a648a6/e3e4e8.png";
const img2 = "https://dummyimage.com/400x200/4e49a6/e3e4e8.png";
const img3 = "https://dummyimage.com/400x200/077d32/e3e4e8.png";
let [lsNum, setLsNum] = useState(0);
// Absolute number '1'
let count = useRef(1);
const list = [img1, img2, img3];
useEffect(() => {
// Get value from localStorage, transform to number
const lS = Number(localStorage.getItem("image"));
// Check! If localStorage have number 2 / more
// Set number 0
if (lS >= 2) {
localStorage.setItem("image", 0);
return;
}
// Get absolute number and increase with number from localStorage
count.current = count.current + lS;
// Set result to state
setLsNum(count.current);
// Set the resulting number to localStorage
localStorage.setItem("image", count.current);
}, []);
const css = {
height: "200px",
width: "400px",
display: "block",
backgroundImage: `url(${list[lsNum]})`, // index change
backgroundPosition: "center",
backgroundSize: "cover",
border: "1px solid red"
};
return (
<div className="App">
<div style={css}></div>
</div>
);
}
Hi i'm trying to get the screenX event(e.screenX) when the pointer moves on a container/image.
Once i get the screenX value it should show "i'm Left" if < 50% and show "i'm Right" if >=50%.
I've tried to get the screenX but i'm getting the console.log as "undefined" and not a value.
Below is my code:
import { useEffect, useState } from "react";
function useScreenX() {
const [screenPosition, setScreenPosition] = useState(0);
function handleDocument() {
const { screenX: currentScreenX } = document.querySelector("#screen-log"); // I'm doing something wrong here//
setScreenPosition(currentScreenX);
console.log(currentScreenX);
}
useEffect(() => {
window.addEventListener("mousemove", handleDocument);
return () =>
window.removeEventListener("mousemove", handleDocument);
}, []);
return (
<div id="screen-log">
<h1>My Container</h1>
</div>
);
}
export default useScreenX;
CSS Style:
#screen-log {
background-color: gold;
width: 100vw;
height: 200px;
}
You are almost there. Just few minor corrections to make:
you should use the event in your handleDocument
screenX returns a number and you are trying to store it as an object
you need mouse movements to be tracked on specific div but you are attaching the listener to the entire window.
The handleDocument function automatically get's the event object. Use it to get screenX. Also obtain the element in useEffect and attach the listener to the element.
Working copy of your code is here in the codesandbox
Code Snippet:
export default function App() {
const [screenPosition, setScreenPosition] = useState(0);
function handleDocument(e) {
const screenX = e.screenX;
setScreenPosition(screenX);
console.log(screenX);
}
useEffect(() => {
document
.getElementById("screen-log")
.addEventListener("mousemove", handleDocument);
return () =>
document
.getElementById("screen-log")
.removeEventListener("mousemove", handleDocument);
}, []);
return (
<div id="screen-log">
<h1>My Container</h1>
</div>
);
}
I am working on a canvas library and I have the following class:
export default class Layer {
constructor(ypcanvas) {
this.ypcanvas = ypcanvas;
this.container = ypcanvas.container;
this.can = document.createElement("canvas");
this.ctx = this.can.getContext("2d");
}}
(This is just a little excerpt of the Layer class)
You can import the class and then create a new Layer like this:
let layer = new Layer(ypcanvas);
How could i accomplish an event like the following:
layer.on('mouseout', function () { });
or
layer.mousedown(function () { })
Or somethign equivalent to that, so that the user of my library can just call that event without having to addEventListener the layer canvas.
Thx in advance.
You can do something like this:
class MyLibClass {
constructor(elementId) {
this.elementId = elementId
}
mouseDown(callback) {
document.getElementById(this.elementId).addEventListener("mousedown", callback)
}
}
new MyLibClass("lib").mouseDown(() => alert("hey"))
#lib {
width: 100px;
height: 100px;
background: blue;
}
<div id="lib"></div>
Assuming you're going to add the canvas somehow to the document tree – otherwise you should describe more in details your system –, you can just proxy the lister to the canvas:
export default class Layer {
constructor(ypcanvas) {
this.ypcanvas = ypcanvas;
this.container = ypcanvas.container;
this.can = document.createElement("canvas");
this.ctx = this.can.getContext("2d");
}
on(type, lister) {
this.can.addEventListener(type, lister);
}
off(type,lister) {
this.can.removeEventListener(type, lister);
}
}
If instead it's just a memory canvas, then you have to implement a coordinates system and z-index to emit the right event from the physical canvas based on the layer copied – and that is indeed more complicated.
However, it's still the same principle: if you don't want that the final user adds the events to the canvas, you have to do it yourself at a certain point.
I developed a game using module scripts in JavaScript. Basically import and export statement inside my code. The game is only available running local server but I want it available through index.html. If I open it using index.html it throws
Access to Script at path from origin 'null' has been blocked by CORS policy: Invalid response. Origin 'null' is therefore not allowed access.
<!DOCTYPE html>
<html>
<head>
<title>My Game</title>
<style>
body{
margin: 0;
height: 100%;
width: 100%;
}
#screen {
display: block;
height: 100vh;
width: 100vw;
margin: 0;
image-rendering: pixelated;
}
</style>
<script type="module" src="js/main.js"></script>
</head>
<body>
<canvas id="screen" width="250" height="330"></canvas>
</body>
</html>
I think I need to rewrite my JS scripts to classic scripts.
Main.js
import Camera from './Camera.js';
import Entity from './Entity.js';
import PlayerController from './traits/PlayerController.js';
import Timer from './Timer.js';
import { loadFont } from './loaders/font.js';
import { loadEntities } from './entities.js';
import { createLevelLoader, createRandomEntity } from './loaders/level.js';
import { setupKeyboard } from './input.js';
import { createCollisionLayer } from './layers/collision.js';
import { createDashboardLayer } from './layers/dashboard.js';
import { createGameOverLayer } from './layers/gameover.js';
function createPlayerEnv(playerEntity) {
const playerEnv = new Entity();
const playerControl = new PlayerController();
playerControl.checkpoint.set(64, 64);
playerControl.setPlayer(playerEntity);
playerEnv.addTrait(playerControl);
return playerEnv;
}
async function main(canvas) {
const context = canvas.getContext('2d');
const [entityFactory, font] = await Promise.all([loadEntities(), loadFont()]);
const loadLevel = await createLevelLoader(entityFactory);
const level = await loadLevel('level');
const camera = new Camera();
const user = entityFactory.user();
const playerEnv = new createPlayerEnv(user);
level.entities.add(playerEnv);
//Bounding Box:
level.comp.layers.push(createCollisionLayer(level));
level.comp.layers.push(createDashboardLayer(font, playerEnv));
timer.start();
}
const canvas = document.getElementById('screen');
main(canvas);
Script with exports
import Keyboard from './KeyboardState.js';
export function setupKeyboard(pacman) {
const input = new Keyboard();
input.addMapping('KeyP', keyState => {
if (keyState) {
pacman.jump.start();
} else {
pacman.jump.cancel();
}
});
input.addMapping('KeyO', keyState => {
pacman.turbo(keyState);
});
input.addMapping('KeyD', keyState => {
pacman.go.dir += keyState ? 1 : -1;
});
input.addMapping('KeyA', keyState => {
pacman.go.dir += keyState ? -1 : 1;
});
return input;
}
Could you provide an example on how to rewrite module scripts to classic scripts? Thanks!