HTML Canvas How can I get the rectangle to move backwards? - css

My code below animates a rectangle to grow right as soon as I start it. How can I make it simultaneously grow left? I have tried (line.x -= line.dx) and a few other variations and it does not seem to work.
const line = {
x: 600,
y: 100,
width: 1,
height: 3,
dx: 1,
dy: 1,
}
let totalLineWidth = line.x + line.width;
const drawLine = () => {
ctx.fillRect(line.x, line.y , line.width, line.height)
}
const update = () => {
drawLine()
line.x += line.dx
requestAnimationFrame(update)
}
update()

At the same time as you draw a new bit to the right, draw a new bit to the left.
The new bit to the left goes an equal amount to the left as the new bit to the right. So that's (600 - (left.x - 600) - and of course update the 600 if you ever change the start position.
Run this snippet in full page and click the button to start the drawing.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const line = {
x: 600,
y: 100,
width: 1,
height: 3,
dx: 1,
dy: 1,
}
let totalLineWidth = line.x + line.width;
const drawLine = () => {
ctx.fillRect(line.x, line.y, line.width, line.height);
ctx.fillRect(600 - (line.x - 600), line.y, line.width, line.height);
}
const update = () => {
drawLine()
line.x += line.dx
requestAnimationFrame(update)
}
//removed just for demo so we can wait until user clicks the button
//update()
canvas {
background-color: pink;
/* just for demo so we can see where the canvas is */
}
<button onclick="update(); this.style.display = 'none';">Click me to start</button>
<br>
<canvas width=1200 height=200></canvas>

Related

How can I change the extent array in vue3-openlayer dynamically

I have several historical maps (around 8kx8k) that I display with a vue3 component. the image and the width and height are passed to these components.
<MapFlex :imgurl="content.maps[5].karte" :width="width" :height="height"></MapFlex>
for this I used the ol-source-image-static
that works quite well so far. The maps are more square, the area for the card is a landscape format rectangle. I now want to show this map fully visible when opened. that works if I don't set an extent in the view.
when I use the extent [-width/2, -height/2, width/2, height/2] I can no longer move the image out of the display field. but I can't make it smaller either. the map fills the view area.
if I want to change the extent rect dynamically depending on the zoom, there is a feedback loop, the system becomes lame and at some point it also stops.
What I actually want to do is that I switch between different extents when changing the zoom.
Or is there a function with which I would like to dynamically generate the appropriate extent. It is important that the map is visible in a smaller size in the display area and can then be zoomed in without being able to slide the map out of the area.
<template>
<div class="map-container">
<ol-map :loadTilesWhileAnimating="true" style="height:980px" >
<ol-view ref="view" :center="center" :rotation="rotation" :zoom="zoom" :maxZoom="maxZoom" :minZoom="minZoom" :projection="projection" :extent="extent" #zoomChanged="onZoomChanged" #resolutionChanged="onResolutionChanged"/>
<ol-image-layer>
<ol-source-image-static :url="imgUrl" :imageSize="size" :imageExtent="imageExtent" :projection="projection"></ol-source-image-static>
</ol-image-layer>
</ol-map>
</div>
</template>
<script>
import { ref, reactive} from 'vue'
export default {
props: {
imgurl: {
type: String,
default: "http://localhost:8080/karten/map-1.jpg",
},
width: {
type: Number,
default: 1024
},
height: {
type: Number,
default: 968
}
},
setup(props) {
const zoom = ref(2)
const minZoom = ref(1)
const maxZoom = ref(6)
const rotation = ref(0)
const faktor = 2
const size = ref([props.width, props.height])
const center = ref( [size.value[0]/2, size.value[1]/2] ); // ref ([0,0]) //
const imageExtent = ref( [-1*props.width/2, -1*props.height/2 , props.width/2, props.height/2 ] )
const extent = ref( [-1 * props.width / faktor, -1 * props.height / faktor, props.width / faktor, props.height / faktor] ) // number[leftBottomX, leftBottomY, rightTopX, rightTopY]
const currentZoom = ref(zoom.value);
const emitter = useEmitter();
console.log(extent);
const projection = reactive({
code: 'xkcd-image',
units: 'pixels',
extent: extent,
const onZoomChanged = (val) => {
currentZoom.value = val;
if (val < 3){
extent.value = [-1 * props.width / 1.5, -1 * props.height / 1.5, props.width /1.5, props.height / 1.5]
}
extent.value = [-1 * props.breite / 1.2, -1 * props.hoehe / 1.2, props.breite / 1.2, props.hoehe / 1.2]
}
const onResolutionChanged = () => {
// console.log("onResolutionChanged");
}
return {
center,
projection,
zoom,
minZoom,
maxZoom,
rotation,
size,
imageExtent,
extent,
imgUrl,
onZoomChanged,
onResolutionChanged,
}
},
}
</script>

How To Crop uploaded image with react-konva

I am using react-konva and I want to crop my selected image when edit button clicked.
Can anyone please guide me how I can achieve this ?
this is the Rect I am using to crop the portion of the image.
Here in this code onShapeChange function saves the crop value of the image in
canvas editor.
{(isCropping &&
<>
{React.createElement(`Rect`, {
ref: cropRef,
key: selectedShape.id,
id: selectedShape.id,
...selectedShape.attributes,
draggable: false,
onTransformEnd: (e) => {
const node = cropRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
const newShape = {
...selectedShape,
attributes:
{
...selectedShape.attributes,
crop: {
x: node.x() - selectedShape.attributes.x,
y: node.y() - selectedShape.attributes.y,
// width: this.state.rect.attrs.width,
// height: this.state.rect.attrs.height
// x: node.x(),
// y: node.y(),
width: Math.max(5, node.width() * scaleX),
height: Math.max(node.height() * scaleY),
}
}
}
console.log('newShape in cropper', newShape, 'SelectedShape', selectedShape);
onShapeChange({
id: selectedShape.id,
index: selectedReportItem.index,
reportIndex: selectedReportItem.reportIndex,
newItem: newShape,
})
setIsCropping(false);
}
}, null)}
<Transformer
ref={croptrRef}
rotateEnabled={false}
flipEnabled={false}
boundBoxFunc={(oldBox, newBox) => {
// limit resize
if (newBox.width < 5 || newBox.height < 5) {
return oldBox;
}
return newBox;
}}
/>
</>
}

slideshow stuck framer motion

I want to make a slide show in framer motion and I found that in framer motion docs they have an example slide show like this https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?from-embed=&file=/src/Example.tsx, but I found a bug when we drag and double click it, it will be stuck like this picture .
import * as React from "react";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { wrap } from "popmotion";
import { images } from "./image-data";
const variants = {
enter: (direction: number) => {
return {
x: direction > 0 ? 1000 : -1000,
opacity: 0
};
},
center: {
zIndex: 1,
x: 0,
opacity: 1
},
exit: (direction: number) => {
return {
zIndex: 0,
x: direction < 0 ? 1000 : -1000,
opacity: 0
};
}
};
const swipeConfidenceThreshold = 10000;
const swipePower = (offset: number, velocity: number) => {
return Math.abs(offset) * velocity;
};
export const Example = () => {
const [[page, direction], setPage] = useState([0, 0]);images.
const imageIndex = wrap(0, images.length, page);
const paginate = (newDirection: number) => {
setPage([page + newDirection, newDirection]);
};
return (
<>
<AnimatePresence initial={false} custom={direction}>
<motion.img
key={page}
src={images[imageIndex]}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 300, damping: 30 },
opacity: { duration: 0.2 }
}}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragEnd={(e, { offset, velocity }) => {
const swipe = swipePower(offset.x, velocity.x);
if (swipe < -swipeConfidenceThreshold) {
paginate(1);
} else if (swipe > swipeConfidenceThreshold) {
paginate(-1);
}
}}
/>
</AnimatePresence>
</>
);
};
I try to solve this problem but still can't fix it, can someone help me?
This looks like a bug of framer-motion.
Up until v1.6.2, everything works fine. The bug seems to occur in all later versions.
There is also an interesting changelog:
[1.6.3] 2019-08-19
Fixed
Ensuring onDragEnd always fires after if onDragStart fired.
Here is a link to the related issue on GitHub, opened by the author of this question.
Until that bug is fixed, here is a workaround that uses Pan events
export default function Carousel() {
const animationConfidenceThreshold = 200; // you have to move the element 200px in order to perform an animation
const [displayed, setDisplayed] = useState(0); // the index of the displayed element
const xOffset = useMotionValue(0); // this is the motion value that drives the translation
const lastOffset = useRef(0); // this is the lastValue of the xOffset after the Pan ended
const elementAnimatingIn = useRef(false); // this will be set to true whilst a new element is performing its animation to the center
useEffect(() => {
// this happens after we have dragged the element out and triggered a rerender
if (elementAnimatingIn.current) {
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
// if the element has animated out to the right it animates in from the left
xOffset.set(
rightPan ? -1 * window.innerWidth - 200 : window.innerWidth + 200
);
// perform the animation to the center
animate(xOffset, 0, {
duration: 0.5,
onComplete: () => {
xOffset.stop();
},
onStop: () => {
elementAnimatingIn.current = false;
lastOffset.current = xOffset.get();
}
});
}
});
return (
<div className="container">
<motion.div
className="carouselElement"
onPan={(e, info) => {
xOffset.set(lastOffset.current + info.offset.x); // set the xOffset to the current offset of the pan + the prev offset
}}
style={{ x: xOffset }}
onPanStart={() => {
// check if xOffset is animating, if true stop animation and set lastOffset to current xOffset
if (xOffset.isAnimating()) {
xOffset.stop();
lastOffset.current = xOffset.get();
}
}}
onPanEnd={(e, info) => {
// there can be a difference between the info.offset.x in onPan and onPanEnd
// so we will set the xOffset to the info.offset.x when the pan ends
xOffset.set(lastOffset.current + info.offset.x);
lastOffset.current = xOffset.get(); // set the lastOffset to the current xOffset
if (Math.abs(lastOffset.current) < animationConfidenceThreshold) {
// if its only a small movement, animate back to the initial position
animate(xOffset, 0, {
onComplete: () => {
lastOffset.current = 0;
}
});
} else {
// perform the animation to the next element
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
animate(
xOffset,
rightPan ? window.innerWidth + 200 : -1 * window.innerWidth - 200, // animate out of view
{
duration: 0.5,
onComplete: () => {
// after the element has animated out
// stop animation (it does not do this on its own, only one animation can happen at a time)
xOffset.stop();
elementAnimatingIn.current = true;
// trigger a rerender with the new content - now the useEffect runs
setDisplayed(rightPan ? displayed - 1 : displayed + 1);
}
}
);
}
}}
>
<span style={{ userSelect: "none" }}>
{"I am element #" + displayed}
</span>
</motion.div>
</div>
);
}
Check this codesandbox out!

Using MathBox2 and A-Frame together

MathBox is great math visualization tool created on top of Three.js and ShaderGraph.js. I'm currently working on explorable explanations for college math and want to use both A-Frame and MathBox in single project (A-Frame driving webVR stuff, scenes, events, physics, roomscale for HTC Vive and MathBox for 2D/3D math visualizations, animations).
I've asked about this in MathBox's google group here: https://groups.google.com/forum/#!topic/mathbox/FwCxKeNQ0-g
Steve Wittens (creator of MathBox) answered:
"A-Frame and MathBox are both based on three.js so the compatibility is possible in theory, but nobody has made the necessary bindings. You'd probably want to look at the examples/test/context.html example to help you figure out how to connect on the MathBox side."
Code from examples/test/context.html that he mentions:
var WIDTH = 640;
var HEIGHT = 480;
// Vanilla Three.js
var renderer = new THREE.WebGLRenderer();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, WIDTH / HEIGHT, .01, 1000);
// Insert into document
document.body.appendChild(renderer.domElement);
// MathBox context
var context = new MathBox.Context(renderer, scene, camera).init();
var mathbox = context.api;
// Set size
renderer.setSize(WIDTH, HEIGHT);
context.resize({ viewWidth: WIDTH, viewHeight: HEIGHT });
// Place camera and set background
camera.position.set(0, 0, 3);
renderer.setClearColor(new THREE.Color(0xFFFFFF), 1.0);
// MathBox elements
view = mathbox
.set({
focus: 3,
})
.cartesian({
range: [[-2, 2], [-1, 1], [-1, 1]],
scale: [2, 1, 1],
});
// Initialize grid (something similar like aframe-gridhelper-component)
view.grid({
divideX: 30,
width: 1,
opacity: 0.5,
zBias: -5,
});
// Animated Math.sin() function
view.interval({
id: 'sampler',
width: 64,
expr: function (emit, x, i, t) {
y = Math.sin(x + t) * .7;
emit(x, y);
},
channels: 2,
});
How should I connect a MathBox and A-Frame?
Maybe a mathbox system?
AFRAME.registerSystem('mathbox', {
init: function () {
var sceneEl = this.el;
if (!sceneEl.renderStarted) {
return sceneEl.addEventListener('renderstart', this.init.bind(this));
}
this.context = new MathBox.Context(sceneEl.renderer, sceneEl.object3D, sceneEl.camera).init();
this.mathbox = this.context.api;
// MathBox elements
this.view = mathbox
.set({
focus: 3,
})
.cartesian({
range: [[-2, 2], [-1, 1], [-1, 1]],
scale: [2, 1, 1],
});
};
}
});
And then you can write components that talk to mathbox.
AFRAME.registerComponent('mathbox-grid', {
init: function () {
var view = this.el.sceneEl.systems.mathbox.view;
view.grid({
divideX: 30,
width: 1,
opacity: 0.5,
zBias: -5,
});
}
});
And then just:
<a-scene>
<a-entity mathbox-grid></a-entity>
</a-scene>
Though you will probably need to bind a lot more stuff like positioning, rotation, scale? You can also prefab it:
AFRAME.registerPrimitive('a-mb-grid', {
defaultComponents: {'mathbox-grid': {}}
});
<a-mb-grid></a-mb-grid>
Alternative / Quick Path
if you don't want the declarative goodness, let A-Frame create the scene/renderer/camera, and use the three.js object3Ds directly in combination with mathbox. Here is documentation about scene and how to access its object3Ds

Moving a shape in kineticJS towards mouse click

I am experimenting with Meteor and KineticJS. What I'm trying to do is to create a shape, and move it towards a mouse click on the stage. The position should be saved to the mongoDB so that all connected clients can be updated.
I haven't gotten far yet, but this is what I've got. I basically need a way to do two things:
How can I make a shape move towards the mouse click on the stage
and stop when it gets there?
Is there a better way of checking
the current position of a shape, other than the gameLoop that I
tried below? It works, but it feels wrong.
Thank you!
//client.js code
var player = null;
var playerAnim = null;
Template.container.rendered = function () {
var myCanvas = document.getElementById('myCanvas');
var stage = new Kinetic.Stage({
container: myCanvas,
width: 1024,
height: 1024
});
var layer = new Kinetic.Layer();
// add the layer to the stage
stage.add(layer);
// add click listener for the stage
$(stage.getContent()).on('click', function(evt) {
//stage was clicked
//Find the player shape in the database
Players.find().forEach(function (dbShape) {
player = new Kinetic.RegularPolygon(dbShape);
// setup an animation to move the player to the right
playerAnim = new Kinetic.Animation(function(frame) {
var velocity = 50;
var dist = velocity * (frame.timeDiff / 1000);
player.move(dist, 0);
Players.update(dbShape._id, {$set: {x: player.attrs.x, y: player.attrs.y}});
}, layer);
playerAnim.start();
layer.add(player);
layer.draw();
});
});
//make a gameLoop to check the position and stop the animation
Meteor.setInterval(function(gameLoop){
if(player.attrs.x > 500){
playerAnim.stop();
}
}, 500);
Meteor.autosubscribe(function () {
// clear the canvas
if (layer) {
layer.removeChildren();
layer.clear();
}
// populate the canvas with the Shapes collection
Players.find().forEach(function (dbShape) {
var player = new Kinetic.RegularPolygon(dbShape);
layer.add(player);
layer.draw();
});
});
}
I would use a tween to do this. Grab your object, get mouse position, and on mousedown or click create a Tween for your node to that mouse position.
layer.on('mousedown', function() {
var mousePos = stage.getMousePosition();
var tween = new Kinetic.Tween({
node: rect,
duration: 1,
x: mousePos.x,
y: mousePos.y,
opacity: 1,
strokeWidth: 6,
scaleX: 1.5
}).play();
});
JSFiddle
Note: To make the layer clickable, we need to add a transparent rectangle with the size of the width and height of the stage to the layer. See in the jsfiddle the Kinetic.Rect I made named var bg
Would putting the check inside the animation work for you?
playerAnim = new Kinetic.Animation(function(frame) {
var velocity = 50;
var dist = velocity * (frame.timeDiff / 1000);
player.move(dist, 0);
Players.update(dbShape._id, {$set: {x: player.attrs.x, y: player.attrs.y}});
if(player.getX() > 500){
this.stop();
}
}, layer);

Resources