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
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>
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;
}}
/>
</>
}
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!
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
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);