Draggable div moving 2px even though it's snapped to 10px - javascript

The following code creates a draggable 40px rectangle inside a 400px grid (using Interact.js):
HTML:
<div id="app">
<div class="live-demo">
<div id="grid-snap"></div>
</div>
</div>
JS:
var element = document.getElementById('grid-snap')
var x = 0
var y = 0
interact(element)
.draggable({
snap: {
targets: [
interact.createSnapGrid({ x: 10, y: 10 })
],
range: Infinity,
relativePoints: [ { x: 0, y: 0 } ]
},
restrict: {
restriction: element.parentNode,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
}
})
.on('dragmove', function (event) {
x += event.dx;
y += event.dy;
event.target.style.webkitTransform =
event.target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
});
CSS:
.live-demo {
display: inline-block;
vertical-align: top;
width: 400px;
height: 400px;
background-color: #e8e9e8;
}
#grid-snap {
width: 40px;
height: 40px;
background-color: #29e;
color: #fff;
}
For some reason, the square moves an initial 2px when it's dragged for the first time. This doesn't happen when createSnapGrid() is set to 30px for x and y.
Not sure if this is a coding or math problem. How to modify the code (or CSS) to eliminate that initial 2px movement?
JSFiddle: https://jsfiddle.net/nq5zz27j/4/

It is something to do with the math. When you set the relativePoint, it is defining where the grid will snap to. So choosing x: 0, Y: 0 will use the top left corner of your object. As to why x: .8 and y: .8 is the answer. I don't have a scientific answer as to why this is (still working on that part).
But if you change
relativePoints: [ { x: 0, y: 0 } ]
to
relativePoints: [ { x: .8, y: .8 } ]
It will do what you are looking for I believe. Let me know if that is what you meant.
jfiddle: https://jsfiddle.net/nq5zz27j/5/

Related

Scroll area by mouse drag in qooxdoo?

Is there any built in possibily to drag content inside container by dragging for example qx.ui.container.Scroll? I found there are qx.ui.core.MDragDropScrolling and qx.ui.core.DragDropScrolling but don't know that is what I am looking for.
Try the code below (eg copy and paste into the Qooxdoo playground).
It traps the mouse movements and apply the relative movement of the mouse (while the button is down) to the scroll bars
// This is a little hack to persuade Chrome to always show the scroll bars
qx.bom.Stylesheet.createElement(
"::-webkit-scrollbar { -webkit-appearance: none; width: 7px; }\n" +
"::-webkit-scrollbar-thumb { border-radius: 4px; background-color: rgba(0, 0, 0, .5); box-shadow: 0 0 1px rgba(255, 255, 255, .5); }\n"
);
// This is a simple thing to add a border to our bigWidget
var border = new qx.ui.decoration.Decorator().set({
width: 3,
style: "solid",
color: "black"
});
// Creates a massive widget
var bigWidget = new qx.ui.core.Widget().set({
minWidth: 2000,
minHeight: 2000,
backgroundColor: "red",
decorator: border
});
// Scrollable area
var scrollable = new qx.ui.container.Scroll(bigWidget).set({
scrollbar: [ "on", "on" ]
});
var mouseDown = false;
var mouseStartPos = null;
var widgetStartPos = null;
bigWidget.addListener("mousedown", evt => {
mouseDown = true;
mouseStartPos = { top: evt.getScreenTop(), left: evt.getScreenLeft() };
widgetStartPos = { top: scrollable.getScrollX(), left: scrollable.getScrollY() };
});
bigWidget.addListener("mouseup", () => {
mouseDown = false;
});
bigWidget.addListener("mousemove", evt => {
if (!mouseDown || !evt.isLeftPressed())
return;
let deltaPos = { top: mouseStartPos.top - evt.getScreenTop(), left: mouseStartPos.left - evt.getScreenLeft() };
scrollable.scrollToX(widgetStartPos.left - deltaPos.left);
scrollable.scrollToY(widgetStartPos.top - deltaPos.top);
console.log("deltaPos=" + JSON.stringify(deltaPos) + ", mouseStartPos=" + JSON.stringify(mouseStartPos) + ", widgetStartPos=" + JSON.stringify(widgetStartPos));
});
var doc = this.getRoot();
doc.add(scrollable, {
left : 0,
top : 0,
right: 0,
bottom: 0
});

How do I get a smooth interpolation between keyframes?

I am trying to make an animation system for my three.js project. I have a json file for the information. I was able to make the animation play. But, at the moment they need to be somewhere, they move to that location, instead of slowly moving to that location over time. The json file tells the program where the specific object needs to be at a certain location. For example:
Json File:
{
"right": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, 1, 0],
"0.5": [1, 1, 0]
}
}
Json files tells you position, at what second, then the positions.
Code:
for (const [key, value] of Object.entries(json.position.right))
if(seconds === json.position.right[key]) {
obj.position.x = json.right.position[key][0];
obj.position.y = json.right.position[key][1];
obj.position.z = json.right.position[key][2];
}
}
In the code, I loop through the json file's right cube position (which tells when the position changes happen). If the seconds match, it moves to that position.
How would I be able to get the movement inbetween the keyframes for the object?
Here is the example: https://mixed-polyester-a.glitch.me/
Here is all the code: https://glitch.com/edit/#!/mixed-polyester-a
I used Blockbench to export models as .OBJ files, materials as .MTL files, and animations as .JSON files.
Sorry if it sounds confusing, didn't really know how to explain it. Any help would be highly appreciated.
Three.js has a method called MathUtils.lerp() that takes in a starting position, an ending position, and an interpolation value between [0, 1]. You could use this to tween your object's current position to its target destination on each frame, as demonstrated in the example below:
const container = document.getElementById("container");
const ball = document.getElementById("ball");
// Set current positions
let ballPosX = 0;
let ballPosY = 0;
// Set target positions
let ballTargetX = 0;
let ballTargetY = 0;
// Update target positions on click
function onClick(event) {
ballTargetX = event.layerX;
ballTargetY = event.layerY;
//console.log(event);
}
function update() {
// Interpolate current position towards targets
ballPosX = THREE.MathUtils.lerp(ballPosX, ballTargetX, 0.1);
ballPosY = THREE.MathUtils.lerp(ballPosY, ballTargetY, 0.1);
// Apply current position to our object
ball.style.left = ballPosX + "px";
ball.style.top = ballPosY + "px";
requestAnimationFrame(update);
}
container.addEventListener("click", onClick);
update();
#container {
width: 300px;
height: 300px;
background: #ddd;
}
#ball {
width: 10px;
height: 10px;
position: absolute;
top: 0;
left: 0;
background: #f90;
border-radius: 10px;
margin-top: -5px;
margin-left: -5px;
}
<div id="container">
<div id="ball"></div>
</div>
<script src="https://threejs.org/build/three.js"></script>
Update:
To create a linear timeline with keyframes, I've used gsap.to() with the keyframes parameter to feed all the positions to the timeline. See here and look up "keyframes" for more details. You can see it in action in the code demo below, you'll need to iterate through your JSON to feed that data to GSAP on your own, though. Good luck!
// Set position vector
const ballPos = {x: 0, y: 0};
const positions = {
"0.0": [0, 0],
"0.25": [0, 100],
"0.5": [100, 100],
"0.75": [100, 0],
"1.0": [0, 0],
}
const timeline = gsap.to(ballPos, {keyframes: [
{x: positions["0.0"][0], y: positions["0.0"][1], duration: 0.0},
{x: positions["0.25"][0], y: positions["0.25"][1], duration: 0.25},
{x: positions["0.5"][0], y: positions["0.5"][1], duration: 0.25},
{x: positions["0.75"][0], y: positions["0.75"][1], duration: 0.25},
{x: positions["1.0"][0], y: positions["1.0"][1], duration: 0.25},
]});
const container = document.getElementById("container");
const ball = document.getElementById("ball");
let timelineTime = 0;
function update() {
timelineTime += 0.001;
timelineTime %= 1;
timeline.seek(timelineTime);
// Apply current position to our object
ball.style.left = ballPos.x + "px";
ball.style.top = ballPos.y + "px";
requestAnimationFrame(update);
}
update();
#container {
width: 300px;
height: 300px;
background: #ddd;
}
#ball {
width: 10px;
height: 10px;
position: absolute;
top: 0;
left: 0;
background: #f90;
border-radius: 10px;
margin-top: -5px;
margin-left: -5px;
}
<div id="container">
<div id="ball"></div>
</div>
<script src="https://threejs.org/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.1/gsap.min.js"></script>

Trying the native Element.animate method, but cannot add keyframe percentages

Someone asked a related question and I am trying to make it work with the experimental technology Element.animate.
The MDN documentation is rather...slim and the state of the technology is lets say infantile.
https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
What I have is this markup:
<div class="container">
<div id="movetxt">left to right, right to left</div>
</div>
Just want the movetxt div to move from left to right ad infinity within the container div.
Css is hard coded, as I do not know how to get the width of the div text node.
.container {
margin: 0 auto;
width: 300px;
border: 1px solid red;
}
#movetxt {
width: 180px;
}
Now, the JS
var container = document.querySelector(".container");
var movingText = document.getElementById("movetxt");
var containerWidth = container.offsetWidth;
var textWidth = movingText.offsetWidth;
var totalDistance = containerWidth - textWidth;
var oneWayDistance = totalDistance / 2; //just establishing the travel distance
And now the experiment part:
movingText.animate([
// keyframes
{ transform: 'translateX(' + oneWayDistance + 'px)' },
{ transform: 'translateX(-' + totalDistance + 'px)' }
], {
// timing options
duration: 1000,
iterations: Infinity
});
This kind of works, the console throws some errors, but the thing runs.
But, I want to have this kind of keyframas format, just an example:
0% { transform: 'translateX(' + oneWayDistance + 'px)' },
10% { transform: 'translateX(-' + totalDistance + 'px)' }
If I try that, I get Failed to execute 'animate' on 'Element': Keyframes must be objects, or null or undefined.
Link to fiddle:
http://jsfiddle.net/vdb3ofmL/1135/
There must be some way to place the keyframe % values there, can anyone figure this out?
Cheers
You need to consider the fact that the text is initially on the left edge of the container, so you need to translate form 0 to width of container - width of text:
And what you are looking for is the offset valueref
var container = document.querySelector(".container");
var movingText = document.getElementById("movetxt");
var containerWidth = container.offsetWidth;
var textWidth = movingText.offsetWidth;
var totalDistance = containerWidth - textWidth;
var oneWayDistance = totalDistance / 2;
console.log(containerWidth);
console.log(textWidth);
movingText.animate([
// keyframes
{ transform: 'translateX(0)',offset:0 }, /*0%*/
{ transform: 'translateX(' + totalDistance + 'px)',offset:0.3 }, /*30%*/
{ transform: 'translateX(0)',offset:1 } /*100%*/
], {
// timing options
duration: 1000,
iterations: Infinity
});
.container {
margin: 0 auto;
width: 300px;
border: 1px solid red;
}
#movetxt {
width: 180px;
border:1px solid;
}
<div class="container">
<div id="movetxt">left to right, right to left</div>
</div>
You should use offset like this
element.animate({
opacity: [ 0, 0.9, 1 ],
offset: [ 0, 0.8 ], // Shorthand for [ 0, 0.8, 1 ]
easing: [ 'ease-in', 'ease-out' ],
}, 2000);
and in your case you can use like this and change your own
var container = document.querySelector(".container");
var movingText = document.getElementById("movetxt");
var containerWidth = container.offsetWidth;
var textWidth = movingText.offsetWidth;
var totalDistance = containerWidth - textWidth;
var oneWayDistance = totalDistance / 2;
movingText.animate(
{transform:['translateX(' + oneWayDistance + 'px)','translateX(-' + totalDistance + 'px)','translateX(' + oneWayDistance + 'px)'],
offset: [0,0.1]
}, {
duration: 1000,
iterations: Infinity
});
.container {
margin: 0 auto;
width: 300px;
border: 1px solid red;
}
#movetxt {
width: 180px;
}
<div class="container">
<div id="movetxt">left to right, right to left</div>
</div>

How to insert a faceless square of a person into an image?

The user forwards an image and receives a response through a json on that image, one of the answers is as follows:
"faceRectangle": {
"top": 187,
"left": 458,
"width": 186,
"height": 186
},
The big question is:
Through the information shown above, how do I insert a square in each
top and left with the javascript?
MY CODE [CSS]
.teste {
border-radius: 0px 0px 0px 0px;
-moz-border-radius: 0px 0px 0px 0px;
-webkit-border-radius: 0px 0px 0px 0px;
border: 5px solid #efff0d;
}
MY CODE [HTML]
<div class="col s12 m12 l6 xl6 center-align">
<div class="container-image-uploaded">
<div id="teste"></div>
<img id="sourceImagem" class="responsive-img sourceImagem">
</div>
</div>
MY CODE [JAVASCRIPT]
document.getElementById('teste').innerHTML.style.width += obj[o].faceRectangle.width + "px";
document.getElementById('teste').innerHTML.style.height += obj[o].faceRectangle.height + "px";
document.getElementById('teste').innerHTML.style.top += obj[o].faceRectangle.top + "px";
document.getElementById('teste').innerHTML.style.left += obj[o].faceRectangle.left + "px";
I would prefer to use appendChild because then you can create your new dom elements in memory and add it with this method.
.innerHtml is also working but then you're more working with strings.
Please have a look at the demo below or this fiddle.
Vue.js is not really needed in this example but with it's easier to work component based. If you don't know Vue then just have a look at the add method that's called by the mousedown eventhandler markImage.
As O'Kane mentioned in the comments, you need to be careful with your string concatenation. I'm using back-tick syntax / template string for the strings in my demo so you can easily insert your values into the string. e.g.
var style = `
left: ${pos.x}px;
top: ${pos.y}px;`
The same is also possible with single quotes but that's harder to write. e.g.
var style = 'left: ' + pos.x + 'px; top: ' + pos.y + 'px;'
Note: Babel is required to work with template strings. With-out it browser support is limited see here
In the demo I've created two variants:
SVG rectangles (less css required to make it work)
DIVs like your code (requires more css)
I think both variants are working and OK. I would probably use SVG because it's easier to create and also exporting later is no problem. e.g. if you like to save it as a new image.
A note to the css
The following zero-space width character is needed to have the div react on the size specified. Probably setting min-width would also work.
.image-marker-png:after {
content: '\200b';
}
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
const svgOutput = {
mounted () {
this.svg = this.$el.querySelector('svg')
let img = document.createElementNS('http://www.w3.org/2000/svg', 'image')
img.setAttribute('x', 0)
img.setAttribute('y', 0)
img.setAttribute('width', '100%')
img.setAttribute('height', '100%')
img.setAttribute('href', 'https://unsplash.it/300')
console.log(img)
this.svg.appendChild(img)
// setInterval(this.add, 50)
this.image = this.svg.querySelector('image')
this.image.addEventListener('mousedown', this.markImage)
},
beforeDestroy() {
// clean listeners
this.image.removeEventListener('mousedown', this.markImage)
},
template: `
<div>
<svg width="300px" height="300px" xmlns="http://www.w3.org/2000/svg"></svg>
<button #click="add">add random</button>
<button #click="add($event, {x: 20, y: 40}, {width: 50, height: 100})">
add "x20,y40,w50,h100"
</button>
</div>
`,
methods: {
add (evt, pos, dimension, color) {
let rectangle = document.createElementNS("http://www.w3.org/2000/svg", 'rect')
let containerSize = this.svg.getBoundingClientRect();
dimension = dimension || {}
pos = pos || {}
let sizeX = dimension.width || 50;
let sizeY = dimension.height || 50;
let x = pos.x || (Math.random()*(containerSize.width) - sizeX)
let y = pos.y || (Math.random()*(containerSize.height) - sizeY)
// some limiting
if (x > containerSize.width) {
x = containerSize.width - sizeX + 1
}
if (x < 0) {
x = 1
}
if (y > containerSize.height) {
y = containerSize.height - sizeY + 1
}
if (y < 0) {
y = 1
}
rectangle.setAttribute('class', 'image-marker')
rectangle.setAttribute('width', sizeX)
rectangle.setAttribute('height', sizeY)
rectangle.setAttribute('x', x)
rectangle.setAttribute('y', y)
rectangle.setAttribute('fill', color || getRandomColor())
rectangle.setAttribute('stroke', 'black')
this.svg.appendChild(rectangle)
},
markImage (evt) {
console.log('clicked svg', evt)
this.add(evt, {x: evt.offsetX, y: evt.offsetY}, {width: 20, height: 20})
}
}
}
const imgOutput = {
beforeDestroy() {
// clean listeners
this.container.removeEventListener('mousedown', this.markImage)
},
mounted() {
this.container = this.$el
this.container.addEventListener('mousedown', this.markImage)
},
template: `
<div class="image-container">
<img src="https://unsplash.it/300"/>
</div>
`,
methods: {
add (evt, pos, dimension, color) {
let marker = document.createElement('div')
marker.setAttribute('class', 'image-marker-png')
marker.setAttribute('style', `
left: ${pos.x}px;
top: ${pos.y}px;
width: ${dimension.width}px;
height: ${dimension.height}px;
background-color: ${color || getRandomColor()}`)
console.log('add marker', marker)
this.container.appendChild(marker)
},
markImage (evt) {
console.log('clicked image', evt)
this.add(evt, {x: evt.offsetX, y: evt.offsetY}, {width: 20, height: 20})
}
}
}
console.log(imgOutput)
new Vue({
components: {
svgOutput,
imgOutput
},
el: '#app'
})
.image-marker:hover {
stroke-width: 1px;
stroke: yellow;
}
.image-container {
position: relative;
display: inline-block;
overflow: hidden;
}
.image-marker-png {
position: absolute;
z-index: +1;
}
.image-marker-png:hover{
border: 1px solid yellow;
}
.image-marker-png:after {
content: '\200b';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<svg-output></svg-output>
<img-output></img-output>
</div>

mo.js animation for multiple items

I need your help.
To apply animation to multiple elements with a single class of "my-button"? Now this only works for a single button.
Replacement querySelector on querySelectorAll not solve the problem, the script becomes not working
Thank you.
var el = document.querySelector('.my-button'),
elSpan = el.querySelector('span'),
// mo.js timeline obj
timeline = new mojs.Timeline(),
scaleCurve = mojs.easing.path('M0,100 L25,99.9999983 C26.2328835,75.0708847 19.7847843,0 100,0'),
// tweens for the animation:
// burst animation
tween1 = new mojs.Burst({
parent: el,
duration: 1500,
shape : 'circle',
fill : [ '#e67e22', '#DE8AA0', '#8AAEDE', '#8ADEAD', '#DEC58A', '#8AD1DE' ],
x: '50%',
y: '50%',
opacity: 0.8,
childOptions: { radius: {20:0} },
radius: {40:120},
angle: {0: 180},
count: 8,
isSwirl: true,
isRunLess: true,
easing: mojs.easing.bezier(0.1, 1, 0.3, 1)
}),
// ring animation
tween2 = new mojs.Transit({
parent: el,
duration: 750,
type: 'circle',
radius: {0: 50},
fill: 'transparent',
stroke: '#2ecc71',
strokeWidth: {15:0},
opacity: 0.6,
x: '50%',
y: '50%',
isRunLess: true,
easing: mojs.easing.bezier(0, 1, 0.5, 1)
}),
// icon scale animation
tween3 = new mojs.Tween({
duration : 900,
onUpdate: function(progress) {
if(progress > 0.3) {
var scaleProgress = scaleCurve(progress);
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(' + scaleProgress + ',' + scaleProgress + ',1)';
elSpan.style.WebkitTransform = elSpan.style.color = '#2ecc71';
} else {
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(0,0,1)';
elSpan.style.WebkitTransform = elSpan.style.color = 'rgba(0,0,0,0.3)';
}
}
});
// add tweens to timeline:
timeline.add(tween1, tween2, tween3);
// when clicking the button start the timeline/animation:
el.addEventListener('mousedown', function() {
timeline.start();
});
.wrapper {
display: flex;
justify-content: center;
align-items: center;
align-content: center;
text-align: center;
height: 200px;
}
.my-button {
background: transparent;
border: none;
outline: none;
margin: 0;
padding: 0;
position: relative;
text-align:center;
}
svg {
top: 0;
left: 0;
}
.send-icon {
position: relative;
font-size: 40px;
color: rgba(0,0,0,0.3);
}
<link href="http://fontawesome.io/assets/font-awesome/css/font-awesome.css" rel="stylesheet"/>
<script src="http://netgon.net/mo.min.js"></script>
<div class="wrapper">
<button class="my-button">
<span class="send-icon fa fa-paper-plane"></span>
</button>
</div>
Codepen
Working CodePen Here!
the "parent element" el must be a single element, so achieving this would be tricky and create a complicated messy code.
so I created a different method that'll create the animations once and set their position using tune() when a button is clicked, this will improve performance since you only have one animation object in the DOM.
instead of creating three animations for each parent/button.
I listen to the all buttons with the class "my-button" set animation's top and left values accordingly
$( ".my-button" ).on( "click", function() {
aniPos = findCenter($(this));
myAnimation1.tune({ top: aniPos.y, left: aniPos.x });
myAnimation2.tune({ top: aniPos.y, left: aniPos.x });
myTimeline.replay();
anipos is calculated with a simple function that'll return an object with .x and .y values
function findCenter ($this) {
var offset = $this.offset();
var width = $this.width();
var height = $this.height();
IDs = {
x: offset.left + (width / 2),
y: offset.top + (height / 2)
};
console.log(offset);
return IDs;
}
I also added a method to animate the clicked button automatically.
elSpan = this.querySelector('span');
new mojs.Tween({
duration : 900,
onUpdate: function(progress) {
if(progress > 0.3) {
var scaleProgress = scaleCurve(progress);
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(' + scaleProgress + ',' + scaleProgress + ',1)';
elSpan.style.WebkitTransform = elSpan.style.color = '#2ecc71';
} else {
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(0,0,1)';
elSpan.style.WebkitTransform = elSpan.style.color = 'rgba(0,0,0,0.3)';
}
}
}).play();
});
please note that my example will require JQuery to work.
I used the exact animations you provided but you can change them as long as you don't change X, Y, top and left properties

Categories