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);
Related
I am developing a custom element representing a window in an operating system. Like a window, it should be moveable/draggable. I can successfully move the window with this code:
moveVertically(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.setTop(boundingClientRect.top + movement)
this.setBottom(boundingClientRect.bottom + movement)
}
moveHorizontally(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.setLeft(boundingClientRect.left + movement)
this.setRight(boundingClientRect.right + movement)
}
The argument movement is mouse event variable movementY and movementX, respectively. The problem is that, if I move the window to quickly, it resizes as well.
How do I move the window without resizing it?
Edit: With this code:
moveVertically(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.style.transform =
`translateY(${boundingClientRect.top + movement}px)`
}
moveHorizontally(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.style.transform =
`translateX(${boundingClientRect.left + movement}px)`
}
the window kind of just moves up and down, when I try to move it. If I comment one of the methods out, it works perfectly, but I can't get both to work at the same time.
I haven't changed my resizing method yet, but that probably doesn't matter.
The reason why your second attempt doesn't work is because you are overwriting the CSS transform. You will need to persist the values on either axis: this can be done by using CSS custom properties for example.
The example below is an adapted example based on your code. The --translate-x and --translate-y are CSS custom properties that can be adjusted individually and they are simply being assigned to the CSS transform property using transform: translate(var(--translate-x, 0), var(--translate-y, 0)); (the fallback value is set to 0):
const stage = document.querySelector('#stage');
const el = document.querySelector('#el');
function moveVertically(movement) {
el.style.setProperty('--translate-y', `${movement}px`);
}
function moveHorizontally(movement) {
el.style.setProperty('--translate-x', `${movement}px`);
}
stage.addEventListener('mousemove', e => {
moveHorizontally(e.clientX);
moveVertically(e.clientY);
});
body {
margin: 0;
padding: 0;
}
#stage {
position: relative;
width: 100vw;
height: 100vh;
}
#el {
position: absolute;
width: 100px;
height: 100px;
background-color: steelblue;
transform: translate(var(--translate-x, 0), var(--translate-y, 0));
}
<div id="stage">
<div id="el"></div>
</div>
Alternatively you can also store the XY coordinates into a variable:
const stage = document.querySelector('#stage');
const el = document.querySelector('#el');
const elCoords = { x: 0, y: 0 };
function moveVertically(movement) {
elCoords.y = movement;
}
function moveHorizontally(movement) {
elCoords.x = movement;
}
stage.addEventListener('mousemove', e => {
moveHorizontally(e.clientX);
moveVertically(e.clientY);
el.style.transform = `translate(${elCoords.x}px, ${elCoords.y}px)`;
});
body {
margin: 0;
padding: 0;
}
#stage {
position: relative;
width: 100vw;
height: 100vh;
}
#el {
position: absolute;
width: 100px;
height: 100px;
background-color: steelblue;
}
<div id="stage">
<div id="el"></div>
</div>
I have created a card tracker for a game.
It is based on a grid pattern of different divs that overlay a background image. Within these divs are smaller images that represent the cards and can be dragged from one div to the next.
I would like the divs to scale with the window, but they need to retain their aspect ratio so that they still overlay the correct parts of the background image as that scales.
I can get this working using "padding-top" for the divs; however, this then makes the smaller appear at the very bottom of that div and extends the div, and prevents them being dragged out and I can't see why it has this effect.
Many thanks in advance for any help!
Here's an example div without padding-top applied:
#div33 {
position: absolute;
top: 250px;
left: 542px;
width: 270px;
height: 350px;
margin: 2px;
padding: 2px;
background-color: rgba(255, 184, 53, 0.);
}
The draggable elements:
.draggable {
padding: 0px;
background-color: rgba(255, 255, 255, 0);
cursor: move;
padding: 1px;
}
.draggable.dragging {
opacity: .1;
}
and the script for dragging and dropping:
<script>
const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.container')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
containers.forEach(container => {
container.addEventListener('dragover', e => {
e.preventDefault()
const afterElement = getDragAfterElement(container, e.clientY)
const draggable = document.querySelector('.dragging')
if (afterElement == null) {
container.appendChild(draggable)
} else {
container.insertBefore(draggable, afterElement)
}
})
})
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect()
const offset = y - box.top - box.height / 2
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child }
} else {
return closest
}
}, { offset: Number.NEGATIVE_INFINITY }).element
}
</script>
I think I have answered my own question. I've just found out about the newer "aspect-ratio" property.
I want to implement a zoom effect on certain areas of the image on hover. I tried to do it but can't think of a correct solution.
let's say I can't enlarge the top right corner.
window.addEventListener('DOMContentLoaded', () => {
const wrapper = document.querySelector('.wrapper');
const img = document.querySelector('img');
const handleMouseMove = (e) => {
const { left, top, width, height } = e.target.getBoundingClientRect();
const x = ((e.pageX - left) / width) * 100;
const y = ((e.pageY - top) / height) * 100;
const styles = `transform: scale(2); transform-origin: ${x}px ${y}px`;
img.style = styles;
}
const handleMouseLeave = () => {
img.style = '';
console.log('leave');
}
wrapper.addEventListener('mousemove', (e) => handleMouseMove(e));
wrapper.addEventListener('mouseleave', handleMouseLeave);
});
.wrapper {
width: 300px;
height: 300px;
margin: 50px auto;
overflow: hidden;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
<div class="wrapper">
<img src="https://img.freepik.com/free-photo/adorable-brown-white-basenji-dog-smiling-giving-high-five-isolated-white_346278-1657.jpg?size=626&ext=jpg&ga=GA1.2.1449299337.1618704000" alt="">
</div>
I could understand your objective. The principal error was that you had been trying to update the zoom in basis of the current position of the image, which was constantly changing because you were changing the image properties.
In my solution I used the left, and top variables to make it the most generic possible, it could be easier in React.js in my opinion.
I take the top and left position on the first load, that the real solution.
window.addEventListener('DOMContentLoaded', () => {
const wrapper = document.querySelector('.wrapper');
const img = document.querySelector('img');
const { left, top } = wrapper.getBoundingClientRect();
const handleMouseMove = (e) => {
const x = (e.pageX - left);
const y = (e.pageY - top);
const styles = `transform: scale(2); transform-origin: ${x}px ${y}px`;
img.style = styles;
}
const handleMouseLeave = () => {
img.style = '';
}
wrapper.addEventListener('mousemove', (e) => handleMouseMove(e));
wrapper.addEventListener('mouseleave', handleMouseLeave);
});
.wrapper {
width: 300px;
height: 300px;
margin: 50px auto;
overflow: hidden;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
<div class="wrapper">
<img src="https://img.freepik.com/free-photo/adorable-brown-white-basenji-dog-smiling-giving-high-five-isolated-white_346278-1657.jpg?size=626&ext=jpg&ga=GA1.2.1449299337.1618704000" alt="">
</div>
You made the calculations way more complicated than you had to.
I changed ...
const x = ((e.pageX - left) / width) * 100;
const y = ((e.pageY - top) / height) * 100;
... to ...
const x = e.pageX - left;
const y = e.pageY - top;
but the real magic happens when I added pointer-events: none; to img so it's always sending in .wrapper as e.target.
NOTE: the zoom functionality bugs out if the image is "over" the edge of the page, i.e. if you need to scroll down to see the full image. This happens in the original code as well. Just a heads up.
window.addEventListener('DOMContentLoaded', () => {
const wrapper = document.querySelector('.wrapper');
const img = document.querySelector('img');
const handleMouseMove = (e) => {
const { left, top, width, height } = e.target.getBoundingClientRect();
const x = e.pageX - left;
const y = e.pageY - top;
const styles = `transform: scale(2); transform-origin: ${x}px ${y}px`;
img.style = styles;
}
const handleMouseLeave = () => {
img.style = '';
}
wrapper.addEventListener('mousemove', (e) => handleMouseMove(e));
wrapper.addEventListener('mouseleave', handleMouseLeave);
});
.wrapper {
width: 300px;
height: 300px;
margin: 50px auto;
overflow: hidden;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
pointer-events: none;
}
<div class="wrapper">
<img src="https://img.freepik.com/free-photo/adorable-brown-white-basenji-dog-smiling-giving-high-five-isolated-white_346278-1657.jpg?size=626&ext=jpg&ga=GA1.2.1449299337.1618704000" alt="">
</div>
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>
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>