Interact.js has a snap option to snapping element. But I don't know how to show those guide lines. I was thinking about there might be some events trggered when snapping happened, and I might use them to show those lines, but I didn't find them in the documentation.
interact doesn't support those guidelines out of the box, but you can build them relatively easy for yourself.
I built this fiddle to show the process.
The important part is this:
interact(element)
.draggable({
snap: {
targets: [
function (x, y) {
var newX = Math.round(x / 50) * 50;
var newY = Math.round(y / 50) * 50;
someFunction(newX, newY);
return {
x: newX,
y: newY
};
}],
As you can see, you can build the function that determines the snapping position (newX and newY) in regard to your current mouse position.
In this function you can also call another function. In this case I called a function to show one line if the object is at this position. Here you can get creative. You could set the position of one line Element to the current position, you could set some predefined lines and calculate which is the closest to only show that one, this depends on you business problem.
For more information about interact - snapping, look in here in the docs
might be a bit late but it still can be useful for those who struggle with alignment. Here is my complete approach:
Codepen: https://codepen.io/lakers19/pen/ZEoPpKL
const targets = []
const cleanXLine = () => {
const guideLineX = document.querySelector('.guide-line-x')
guideLineX.style.left = 0
guideLineX.style.top = 0
guideLineX.style.width = 0
guideLineX.style.height = 0
}
const cleanYLine = () => {
const guideLineY = document.querySelector('.guide-line-y')
guideLineY.style.left = 0
guideLineY.style.top = 0
guideLineY.style.width = 0
guideLineY.style.height = 0
}
const resetGuideLine = () => {
cleanXLine()
cleanYLine()
}
const handleStart = (event) => {
// get all interactive elements
targets.length = 0
const elements = document.querySelectorAll('.draggable')
elements.forEach((element) => {
const rect = element.getBoundingClientRect()
const { x, y, width, height } = rect
if (element === event.target) return
const actualX = x + window.scrollX
const actualY = y + window.scrollY
const range = 4
targets.push({
x: actualX,
range,
rect,
element,
})
targets.push({
x: actualX + width,
range,
rect,
element,
})
targets.push({
x: actualX + width / 2,
range,
rect,
element,
})
targets.push({
y: actualY,
range,
rect,
element,
})
targets.push({
y: actualY + height,
range,
rect,
element,
})
targets.push({
y: actualY + height / 2,
range,
rect,
element,
})
})
}
const drawGuideLine = (event) => {
const inRange = event.modifiers.length ? event.modifiers[0]?.inRange : false
if (inRange) {
const guideLineX = document.querySelector('.guide-line-x')
const guideLineY = document.querySelector('.guide-line-y')
const {
x: xModifier,
y: yModifier,
rect,
} = event.modifiers[0].target.source
const { x, y } = event.target.getBoundingClientRect()
if (xModifier) {
guideLineX.style.left = `${xModifier}px`
guideLineX.style.top = `${Math.min(rect.y, y)}px`
guideLineX.style.width = '1px'
guideLineX.style.height = `${Math.abs(rect.y - y)}px`
cleanYLine()
}
if (yModifier) {
console.log(rect.x - x)
guideLineY.style.left = `${Math.min(rect.x, x)}px`
guideLineY.style.top = `${yModifier - window.scrollY}px`
guideLineY.style.width = `${Math.abs(rect.x - x)}px`
guideLineY.style.height = '1px'
cleanXLine()
}
} else {
resetGuideLine()
}
}
interact('.draggable')
.draggable({
// enable inertial throwing
inertia: false,
// keep the element within the area of it's parent
modifiers: [interact.modifiers.snap({
targets: targets,
relativePoints: [
{ x: 0, y: 0 }, // snap relative to the element's top-left,
{ x: 0.5, y: 0.5 }, // to the center
{ x: 1, y: 1 }, // and to the bottom-right
],
}),
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
],
// enable autoScroll
autoScroll: true,
listeners: {
// call this function on every dragmove event
move: dragMoveListener,
start: handleStart,
// call this function on every dragend event
end (event) {
resetGuideLine()
}
}
})
function dragMoveListener (event) {
drawGuideLine(event)
var target = event.target
// keep the dragged position in the data-x/data-y attributes
var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy
// translate the element
target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'
// update the posiion attributes
target.setAttribute('data-x', x)
target.setAttribute('data-y', y)
}
// this function is used later in the resizing and gesture demos
window.dragMoveListener = dragMoveListener
#canvas{
width: 100vw;
height: 100vh;
background: rgb(22, 197, 180);
}
.draggable {
background: rgb(71, 44, 113);
width: 60px;
height: 60px;
}
.draggable:nth-child(1){
translate: 20px 10px;
}
.draggable:nth-child(2){
translate: 50px 60px;
}
body {
display: grid;
place-items: center;
place-content: center;
height: 100%;
}
html{
height: 100%;
}
.guide-line {
pointer-events:none;
background:red;
position:fixed;
display: flex;
justify-items:space-between;
width: 0;
height:0;
left:0;
right:0;
}
.guide-line > span {
font-size: 9px;
line-height: 0;
color: red;
position: absolute;
}
.guide-line-x > span {
transform: translateX(-50%);
left: 50%;
}
.guide-line-y{
flex-direction:row;
}
.guide-line-x {
flex-direction:column;
}
.guide-line-y > span{
transform: translateY(-50%);
top: 50%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.17/interact.min.js"></script>
<div id="canvas">
<div class="draggable" ></div>
<div class="draggable"> </div>
<div class="guide-line-y guide-line">
<span style="opacity:0" >x</span>
<span>x</span>
</div>
<div class="guide-line-x guide-line">
<span style="opacity:0">x</span>
<span>x</span>
</div>
</div>
Related
But When i select the other picture it did not show in magnifier box, instead it show the default picture in magnifier. how i can fix that?. I want to change the image after selecting from below and magnifier should work on that image.
and magnifier position is very downside, can we also make the appropriate position
HTML
Css
.product-image {
height: 300px;
cursor: zoom-in;
}
.magnifier-container {
display: inline-block;
position: relative;
}
.magnifier {
display: none;
position: absolute;
top: 0;
left: 100%;
overflow: hidden;
height: 300px;
width: 300px;
border: 1px solid #ddd;
border-radius: 10px;
background-color: white;
}
.magnifier__img {
width: 1000px;
transform-origin: 150px 150px;
}
js
// most efficient way to add HTML, faster than innerHTML
const parseHTML = (htmlStr) => {
const range = document.createRange();
range.selectNode(document.body); // required in Safari
return range.createContextualFragment(htmlStr);
};
// pass this function any image element to add magnifying functionality
const makeImgMagnifiable = (img) => {
const magnifierFragment = parseHTML(`
<div class="magnifier-container">
<div class="magnifier">
<img class="magnifier__img" src="${img.src}"/>
</div>
</div>
`);
// This preserves the original element reference instead of cloning it.
img.parentElement.insertBefore(magnifierFragment, img);
const magnifierContainerEl = document.querySelector(".magnifier-container");
img.remove();
magnifierContainerEl.appendChild(img);
// query the DOM for the newly added elements
const magnifierEl = magnifierContainerEl.querySelector(".magnifier");
const magnifierImg = magnifierEl.querySelector(".magnifier__img");
// set up the transform object to be mutated as mouse events occur
const transform = {
translate: [0, 0],
scale: 1,
};
// shortcut function to set the transform css property
const setTransformStyle = (el, { translate, scale }) => {
const [xPercent, yRawPercent] = translate;
const yPercent = yRawPercent < 0 ? 0 : yRawPercent;
// make manual pixel adjustments to better center
// the magnified area over the cursor.
const [xOffset, yOffset] = [
`calc(-${xPercent}% + 250px)`,
`calc(-${yPercent}% + 70px)`,
];
el.style = `
transform: scale(${scale}) translate(${xOffset}, ${yOffset});
`;
};
// show magnified thumbnail on hover
img.addEventListener("mousemove", (event) => {
const [mouseX, mouseY] = [event.pageX + 40, event.pageY - 20];
const { top, left, bottom, right } = img.getBoundingClientRect();
transform.translate = [
((mouseX - left) / right) * 100,
((mouseY - top) / bottom) * 100,
];
magnifierEl.style = `
display: block;
top: ${mouseY}px;
left: ${mouseX}px;
`;
setTransformStyle(magnifierImg, transform);
});
// zoom in/out with mouse wheel
img.addEventListener("wheel", (event) => {
event.preventDefault();
const scrollingUp = event.deltaY < 0;
const { scale } = transform;
transform.scale =
scrollingUp && scale < 3
? scale + 0.1
: !scrollingUp && scale > 1
? scale - 0.1
: scale;
setTransformStyle(magnifierImg, transform);
});
// reset after mouse leaves
img.addEventListener("mouseleave", () => {
magnifierEl.style = "";
magnifierImg.style = "";
});
};
const img = document.querySelector(".product-image");
makeImgMagnifiable(img);
So I am making an Angry-Birds game and I am using p5.js and matter.js.
I created a mouseConstraint in the game to move the bird attached to a slingshot, but I am also able to move all the bodies in the output.
How can I attach the mouseConstraint only to a single body, i.e., the bird in this case, so that I can move only that particular object and nothing else?
If this is not possible, is there an alternative which I can use for using the slingshot?
You can use collision filters:
const makeBox = (x, y, w, h, props, elem) => ({
w, h, body: Matter.Bodies.rectangle(
x, y, w, h, props
),
elem,
render() {
const {x, y} = this.body.position;
this.elem.style.top = `${y - this.h / 2}px`;
this.elem.style.left = `${x - this.w / 2}px`;
this.elem.style.transform = `rotate(${this.body.angle}rad)`;
},
});
const boxes = [...document.querySelectorAll(".box")]
.map((el, i) =>
makeBox(
// x y w h
100 * i + 100, 0, 40, 30,
{collisionFilter: i === 0 ? {category: 0b10} : {}},
el,
)
);
const ground = Matter.Bodies.rectangle(
// x y w h
200, 200, 400, 120, {
isStatic: true,
}
);
const engine = Matter.Engine.create();
const mouseConstraint = Matter.MouseConstraint.create(
engine, {
collisionFilter: {mask: 0b10},
element: document.body
}
);
Matter.Composite.add(
engine.world, [
...boxes.map(e => e.body), ground, mouseConstraint
]
);
(function rerender() {
boxes.forEach(e => e.render());
Matter.Engine.update(engine);
requestAnimationFrame(rerender);
})();
.box {
position: absolute;
background: #d00;
transition: background 0.2s;
width: 40px;
height: 30px;
cursor: move;
}
.box:not(:first-child) {
background: #111;
cursor: not-allowed;
}
.box:first-child:hover {
background: #f00;
}
#ground {
position: absolute;
background: #666;
top: 140px;
height: 120px;
width: 400px;
}
html, body {
position: relative;
height: 100%;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
<div id="ground"></div>
Here, the mouse constraint is given a mask of 0b10 and the red box which is the only body that is allowed to interact with the mouse is set to a category of 0b10.
The default mask value is 32 bits all set, or 4294967295/0xffffffff. You may wish to be more precise and disable only the first bit: 0xfffffffe. This lets the mouse constraint interact with other categories in addition to 2, disabling only interactions with category 1.
To create the opposite situation, where the mouse interacts with any bodies except for the red box, you can set the mouse constraint's mask to something that has the secondmost least significant bit off, like 0b1 or 0xfffffffd.
See also:
How can I change the collisionFilter of an object so it can no longer interact with MouseConstraint (MatterJS).
How to prevent sprites to be moved by mouse in Matter.js?
To attach a mouseConstraint to a single body, you'll need to pass in the body as the second argument:
mouseConstraint = MouseConstraint.create(engine, {body: bird});
My implementation,
http://kodhus.com/kodnest/land/PpNFTgp
I am curious, as I am not able for some reason to figure this out, how to get my JavaScript to make my slider behave more natural and smoother, if someone knows, how to, or can make this, feel free. I'd be happy to understand.
JavaScript:
const thumb = document.querySelector('.thumb');
const thumbIndicator = document.querySelector('.thumb .thumb-indicator');
const sliderContainer = document.querySelector('.slider-container');
const trackProgress = document.querySelector('.track-progress');
const sliderContainerStart = sliderContainer.offsetLeft;
const sliderContainerWidth = sliderContainer.offsetWidth;
var translate;
var dragging = false;
var percentage = 14;
document.addEventListener('mousedown', function(e) {
if (e.target.classList.contains('thumb-indicator')) {
dragging = true;
thumbIndicator.classList.add('focus');
}
});
document.addEventListener('mousemove', function(e) {
if (dragging) {
console.log('moving', e)
if (e.clientX < sliderContainerStart) {
translate = 0;
} else if (e.clientX > sliderContainerWidth + sliderContainerStart) {
translate = sliderContainerWidth;
} else {
translate = e.clientX - sliderContainer.offsetLeft;
}
thumb.style.transform = 'translate(-50%) translate(' + translate + 'px)';
trackProgress.style.transform = 'scaleX(' + translate / sliderContainerWidth + ')'
}
});
function setPercentage() {
thumb.style.transform = 'translate(-50%) translate(' + percentage/100 * sliderContainerWidth + 'px)';
trackProgress.style.transform = 'scaleX(' + percentage/100 + ')';
}
function init() {
setPercentage();
}
init();
document.addEventListener('mouseup', function(e) {
dragging = false;
thumbIndicator.classList.remove('focus');
});
EDIT: Is there a way to smoothly and naturally increment by one for every slow move?
Is it possible to make to behave as if, like when one clicks the progress bar so that it jumps there?
The kodhus site is very janky in my browser, so I can't tell if your code lacks responsiveness or whether it's the site itself. I feel that your code is a bit convoluted: translate and width / height are mixed unnecessarily; no need to use a dragging boolean when that information is always stored in the classlist. The following slider performs nicely, and has a few considerations I don't see in yours:
stopPropagation when clicking the .thumb element
drag stops if window loses focus
pointer-events: none; applied to every part of the slider but the .thumb element
let applySliderFeel = (slider, valueChangeCallback=()=>{}) => {
// Now `thumb`, `bar` and `slider` are the elements that concern us
let [ thumb, bar ] = [ '.thumb', '.bar' ].map(v => slider.querySelector(v));
let changed = amt => {
thumb.style.left = `${amt * 100}%`;
bar.style.width = `${amt * 100}%`;
valueChangeCallback(amt);
};
// Pressing down on `thumb` activates dragging
thumb.addEventListener('mousedown', evt => {
thumb.classList.add('active');
evt.preventDefault();
evt.stopPropagation();
});
// Releasing the mouse button (anywhere) deactivates dragging
document.addEventListener('mouseup', evt => thumb.classList.remove('active'));
// If the window loses focus dragging also stops - this can be a very
// nice quality of life improvement!
window.addEventListener('blur', evt => thumb.classList.remove('active'));
// Now we have to act when the mouse moves...
document.addEventListener('mousemove', evt => {
// If the drag isn't active do nothing!
if (!thumb.classList.contains('active')) return;
// Compute `xRelSlider`, which is the mouse position relative to the
// left side of the slider bar. Note that *client*X is compatible with
// getBounding*Client*Rect, and using these two values we can quickly
// get the relative x position.
let { width, left } = slider.getBoundingClientRect();
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Clamp `xRelSlider` between 0 and the slider's width
if (xRelSlider < 0) xRelSlider = 0;
if (xRelSlider > width) xRelSlider = width;
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
slider.addEventListener('mousedown', evt => {
let { width, left } = slider.getBoundingClientRect();
// Clicking the slider also activates a drag
thumb.classList.add('active');
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
changed(0);
};
let valElem = document.querySelector('.value');
applySliderFeel(document.querySelector('.slider'), amt => valElem.innerHTML = amt.toFixed(3));
.slider {
position: absolute;
width: 80%; height: 4px; background-color: rgba(0, 0, 0, 0.3);
left: 10%; top: 50%; margin-top: -2px;
}
.slider > .bar {
position: absolute;
left: 0; top: 0; width: 0; height: 100%;
background-color: #000;
pointer-events: none;
}
.slider > .thumb {
position: absolute;
width: 20px; height: 20px; background-color: #000; border-radius: 100%;
left: 0; top: 50%; margin-top: -10px;
}
.slider > .thumb.active {
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.5);
}
<div class="slider">
<div class="bar"></div>
<div class="thumb"></div>
</div>
<div class="value"></div>
im trying to make a div move with the mouse (horizontal) like a game heres the code :
// Move
setInterval(function () {
$(document.body).one('mousemove',function(e) {
$('.p').animate({
'left': e.pageX
},300);
});
},400);
and it works good (with few lag ) but it works
the problem is if i dont do a thing in the page after 1 min if i try and move the mouse nothing happen
also theres a delay of 10 sec sometimes
how i can fix this ( get better perfomance)
CodePen
const mouse = document.querySelector('.mouse');
const mousePos = { x: 0, y: 0 };
const lastMousePos = { x: 0, y: 0 };
let speedFollow = 0.15;
document.addEventListener('mousemove', e => {
mousePos.x = e.pageX;
mousePos.y = e.pageY;
});
const lerp = (a, b, n) => (1 - n) * a + n * b;
function animateLoop() {
lastMousePos.x = lerp(
lastMousePos.x,
mousePos.x,
speedFollow
);
lastMousePos.y = lerp(
lastMousePos.y,
mousePos.y,
speedFollow
);
mouse.style.transform = `translate(${lastMousePos.x}px, ${lastMousePos.y}px)`;
requestAnimationFrame(animateLoop);
}
requestAnimationFrame(animateLoop);
// other
const inputRange = document.querySelector('input');
const speedFollowBlock = document.querySelector('p b');
inputRange.addEventListener('input', function() {
speedFollow = this.value;
speedFollowBlock.innerHTML = this.value;
});
body {
min-height: 500vh;
}
.mouse {
position: absolute;
z-index: 1;
width: 30px;
height: 30px;
left: -15px;
top: -15px;
background-color: #e74c3c;
border-radius: 50%;
transform: translate(-100%, -100%);
pointer-events: none;
}
<p>Speed Follow: <b>0.15</b></p>
<input type="range" min="0.05" max="1" step="0.05" value="0.15">
<div class="mouse"></div>
I have a page structure that looks like in the image below. When clicking anywhere in the #progress element I want to calculate how many percentages of it's full width that was clicked.
How can this be done?
< div id="progress" onMouseUp={ e => this._seekTo(e) }></div>
...
_seekTo(event) {
var progress = document.getElementById('progress');
console.log((event.clientX - progress.offsetLeft) / progress.offsetWidth * 100)
}
You can get the percentage click position of an elements width along the x-axis like:
document.getElementById('progress').addEventListener('click', function(e) {
var bcr = this.getBoundingClientRect();
console.log('You clicked to ', (e.clientX - bcr.left) / bcr.width);
});
e.clientX provides the horizontal coordinate within the application's client area at which the event occurred. source
Example progress bar:
function mouseSliderPosition(element, e) {
var bcr = element.getBoundingClientRect();
return {
x: Math.min(Math.max(0, (e.clientX - bcr.left) / bcr.width), 1),
y: Math.min(Math.max(0, (e.clientY - bcr.top) / bcr.height), 1)
}
};
function activateSlider(e) {
if (e.touches && e.touches.length > 1) {
return;
}
e.preventDefault();
window.activeSlider = this;
handleSliderMove(e);
}
function handleSliderMove(e) {
if (e.touches && e.touches.length > 1) {
return;
}
if (window.activeSlider) {
var progressBar = window.activeSlider.getElementsByClassName('progress-bar')[0];
var progressFill = window.activeSlider.getElementsByClassName('progress-fill')[0];
var value = mouseSliderPosition(progressBar, e.touches && e.touches[0] || e).x;
progressFill.style.transform = 'scaleX(' + value + ')';
}
}
function deactivateSlider(e) {
if (e.touches && e.touches.length > 0) {
return;
}
this.activeSlider = null;
}
document.querySelector('.progress-slider').addEventListener('mousedown', activateSlider)
document.querySelector('.progress-slider').addEventListener('touchstart', activateSlider)
window.addEventListener('mousemove', handleSliderMove);
window.addEventListener('mouseup', deactivateSlider);
window.addEventListener('touchmove', handleSliderMove);
window.addEventListener('touchend', deactivateSlider);
window.activeSlider = null;
.progress-slider {
padding: 10px;
cursor: pointer;
}
.progress-bar {
height: 2px;
background: rgba(100,100,100,0.5);
}
.progress-slider:hover .progress-bar {
height: 3px;
}
.progress-fill {
height: 100%;
background: rgba(255, 0, 0, 0.7);
transform-origin: 0 50%;
transform: scaleX(0);
}
<div class="progress-slider">
<div class="progress-bar"><div class="progress-fill"></div></div>
</div>