How to make a zoom effect on part of an image? - javascript

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>

Related

On image change magnifier's image is not changing

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);

React sticky canvas trying to hide it

I'm trying to create an scrolling animation with a canvas. The problem I'm running in to is I want to change the "position" from sticky to something else so that it stays in the div.
Because right now that isn't the case as you can see down here.
Image website
If anyone has an idea please let me know
import React, { useRef, useEffect} from 'react';
import './index.css';
export default function animation(){
const html = document.documentElement;
const currentFrame = index => (
`https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero- lightpass/${index.toString().padStart(4, '0')}.jpg`
)
const frameCount = 145;
const canvasHeight = 770;
const canvasWidth = 1158;
const img = new Image();
img.src = currentFrame(1);
const Canvas = props => {
const canvasRef = useRef(null)
useEffect(() => {
const canvas = canvasRef.current
const context = canvas.getContext('2d')
img.onload = function(){
context.drawImage(img,0,0)
}
const updateImage = index =>{
img.src = currentFrame(index)
context.drawImage(img,0,0)
}
window.addEventListener('scroll', ()=>{
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop/maxScrollTop;
const frameIndex = Math.min(frameCount-1, Math.floor(scrollFraction * frameCount))
requestAnimationFrame(()=> updateImage(frameIndex + 1))
})
}, [])
return <canvas ref={canvasRef} {...props}/>
}
return(
<Canvas className="animation" width={canvasWidth} height={canvasHeight}></Canvas>
)
}
html{
height: 100vh;
}
body{
height: 400vh;
}
canvas{
position: -webkit-sticky;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-height: 770px;
max-width: 1158px;
background: rgb(0, 0, 0);
overflow-x: hidden;
}
Set div element position relative.
position: relative;

Sprite frame as div background, auto scales wrongly on div rotation

As the title suggests, I'm rendering a frame of a sprite on a div background.
That all works fine, and resizes correctly just until a rotation is applied on the div.
When the image is first loads in the rotated div, the scale is correct, but if we resize the document, the frame scale becomes incorrect.
Please see this JSFIDDLE. To replicate the issue just resize the "result" section.
Code:
HTML
<div class="img"></div>
CSS
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.img {
overflow: hidden;
background-repeat: no-repeat;
background: url(https://image.freepik.com/free-vector/orange-mushroom-game-sprites_22191-71.jpg);
width: 50vw;
height: 50vw;
}
JS
const $img = document.querySelector('.img');
const firstFrameWidth = 185;
const firstFrameHeight = 155;
const firstFrameX = 0;
const firstFrameY = -10;
const spriteWidth = 626;
const spriteHeight = 348;
function resize() {
const bounds = $img.getBoundingClientRect();
const scaleX = bounds.width / firstFrameWidth;
const scaleY = bounds.height / firstFrameHeight;
const bgWidth = spriteWidth * scaleX;
const bgHeight = spriteHeight * scaleY;
const bgX = firstFrameX * scaleX;
const bgY = firstFrameY * scaleY;
$img.style.backgroundSize = `${bgWidth}px ${bgHeight}px`;
$img.style.backgroundPosition = `${firstFrameX}px ${firstFrameY}px`;
}
window.addEventListener('resize', resize);
resize();
$img.style.transform = 'rotate(45deg)';
Any ideas?
I actually figure out much later that getBoundingClientRect returns different values from window.getComputedStyles...apparently like you said, getBoundingClientRect returns the height and width post rotation, but I needed the original height and width to scale the sprite so the trick was to use window.getComputedStyle.

Issue implementing scroll loop effect and rotating div with offsetTop

Im working on a site right now with a scroll loop effect (when you reach the bottom of the page it seamlessly jumps back to the top creating an endless loop). Though I am having an issue trying implement an effect to rotate the individual div's based on their offsetTop.
Here is a fiddle link with the rotate effect working without the scroll loop effect-> https://jsfiddle.net/jacob_truax/bgrkewny/3/
Here is a link to a fiddle with both effects -> https://jsfiddle.net/jacob_truax/b1x4dow7/18/
As you can see in the second fiddle, adding the scroll loop effect while implementing the rotation effect breaks the code. Can someone help me figure this out please?
Here is the js for the broken fiddle
const sections = document.querySelectorAll("section")
const divTag = document.querySelector("div.Loop")
const mainTag = document.querySelector("main")
var doc = window.document,
clones = divTag.querySelectorAll('.is-clone'),
disableScroll = false,
scrollHeight = 0,
scrollPos = 0,
clonesHeight = 0,
i = 0;
const addMovement = function() {
const topViewport = divTag.offsetTop
const midViewport = topViewport + (divTag.offsetHeight / 2)
sections.forEach(section => {
const topSection = section.offsetTop
const midSection = topSection + (section.offsetHeight / 2)
const distanceToSection = (midViewport - midSection)
console.log(distanceToSection)
const image = section.querySelector(".info")
image.style.transform = `rotate(${distanceToSection}deg)`
})
}
addMovement()
function getScrollPos () {
return (divTag.offsetTop || divTag.scrollTop) - (divTag.clientTop || 0);
}
function setScrollPos (pos) {
divTag.scrollTop = pos;
}
function getClonesHeight () {
clonesHeight = 0;
for (i = 0; i < clones.length; i += 1) {
clonesHeight = clonesHeight + clones[i].offsetHeight;
}
return clonesHeight;
}
function reCalc () {
scrollPos = getScrollPos();
scrollHeight = divTag.scrollHeight;
clonesHeight = getClonesHeight();
if (scrollPos <= 0) {
setScrollPos(1); // Scroll 1 pixel to allow upwards scrolling
}
}
function scrollUpdate () {
if (!disableScroll) {
scrollPos = getScrollPos();
if (clonesHeight + scrollPos >= scrollHeight) {
// Scroll to the top when you’ve reached the bottom
setScrollPos(1); // Scroll down 1 pixel to allow upwards scrolling
disableScroll = true;
} else if (scrollPos <= 0) {
// Scroll to the bottom when you reach the top
setScrollPos(scrollHeight - clonesHeight);
disableScroll = true;
}
}
if (disableScroll) {
// Disable scroll-jumping for a short time to avoid flickering
window.setTimeout(function () {
disableScroll = false;
}, 40);
}
}
function init () {
reCalc();
divTag.addEventListener('scroll', function () {
window.requestAnimationFrame(scrollUpdate);
addMovement()
}, false);
window.addEventListener('resize', function () {
window.requestAnimationFrame(reCalc);
addMovement()
}, false);
}
if (document.readyState !== 'loading') {
init()
} else {
doc.addEventListener('DOMContentLoaded', init, false)
}
Here is the css
html,
body {
height: 100%;
/* overflow: hidden; */
}
body {
color: #000;
}
main {
height: 100%;
position: relative;
top: 0;
left: 0;
}
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
text-align: center;
z-index: 1;
}
.Loop {
position: absolute;
height: 100%;
overflow: auto;
}
section {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
}
::scrollbar {
display: none;
}
section div {
position: absolute;
z-index: 2;
text-align: center;
width: 50%;
background-color: #ff0000;
}
section img {
position: relative;
width: 50%;
background-color: #000;
}
The offsetTop property returns the top position (in pixels) relative
to the top of the offsetParent element.
Changing line #14 to use scrollTop instead works:
const topViewport = divTag.scrollTop;

Move and expand image to fill viewport transition

I'm building a transition where I want to scale up a thumbnail to fill the viewport on click.
Currently I got the calculations right on finding the right scale ratio based on the viewport and it's size, but I'm having problems on centring the image in the viewport after it's scaled/expanded.
I only have the problem when I'm using transform-origin: center center; and the image is centered in a fixed full screen container. Here is a jsfiddle showing my problem.
I've also made a jsfiddle showing how it is without the centering. This is close to what I want, except I want to be able to transition the image from other positions rather than the top left corner.
Here is the code I have so far
// Helper fucntions
const getWindowWidth = () => {
return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
}
const getWindowHeight = () => {
return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
}
// Get element
const El = document.getElementById('test');
// Add listener
El.addEventListener('click', () => {
// Viewport size
const viewH = getWindowHeight();
const viewW = getWindowWidth();
// El size
const elHeight = El.offsetHeight;
const elWidth = El.offsetWidth;
// Scale ratio
let scale = 1;
// Get how much element need to scale
// to fill viewport
// Based on https://stackoverflow.com/a/28475234/1719789
if ((viewW / viewH) > (elWidth / elHeight)) {
scale = viewW / elWidth;
} else {
scale = viewH / elHeight;
}
// Center element
const x = ((viewW - ((elWidth) * scale)) / 2);
const y = ((viewH - ((elHeight) * scale)) / 2);
// matrix(scaleX(),skewY(),skewX(),scaleY(),translateX(),translateY())
El.style.transform = 'matrix(' + scale + ', 0, 0, ' + scale + ', ' + x + ', ' + y + ')';
});
And some css:
.Container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#El {
position: absolute;
top: 50%;
left: 50%;
width: 50%;
transform-origin: center center;
transform: translate(-50%, -50%);
transition: transform .3s ease-in-out;
}
The best solution would be getting the current position of the clicked thumbnail (not always centered) and from that scale it to fill the screen. But I'm not really sure where to start to be able to do that.
Is this the effect you are looking for?
CSS:
.Container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#test {
position: absolute;
top: 50%;
left: 50%;
width: 50%;
transform: translate(-50%, -50%);
transition: all 3.3s ease-in-out;
}
#test.rdy{
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
JS:
// Helper fucntions
const getWindowWidth = () => {
return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
}
const getWindowHeight = () => {
return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
}
const El = document.getElementById('test');
El.addEventListener('click', () => {
const viewH = getWindowHeight();
const viewW = getWindowWidth();
const elHeight = El.offsetHeight;
const elWidth = El.offsetWidth;
let scale = 1;
if ((viewW / viewH) > (elWidth / elHeight)) {
scale = viewW / elWidth;
} else {
scale = viewH / elHeight;
}
El.style.width = elWidth * scale + 'px';
El.style.height = elHeight * scale + 'px';
//Add .rdy class in case that position needs resetting :)
document.getElementById('test').className += ' rdy';
});
//A hack to make height transition work :)
window.onload = function(){
El.style.height = El.offsetHeight + 'px';
};

Categories

Resources