The below code ALMOST works and I think I'm close yet quite far away from getting it to work properly. The problem is, the way the handlePinch function is set up. What actually happens when the user pinches to zoom a bit, is the square starts to accelerate. The more the user pinches, the faster the square zooms.
How can this be implemented that each time the user pinches to zoom, it incrementally scales, i.e. picking up where it left off without exceeding the max scale?
I'm stuck on this for two days and there doesn't seem to be any formula to show how something like this works. Note, I know there are libraries for this, I want to know how this works with vanilla js
const box = document.querySelector('.box')
function updateCss(scale) {
box.style.transform = `scale(${scale})`
}
let lastScale
let currentScale
let isAlreadyScaled = false
const minScale = 1
const maxScale = 1.8
function handlePinch(e) {
e.preventDefault()
const isZooming = e.scale > 1
if (isZooming) {
if (!isAlreadyScaled) {
lastScale = Math.min(minScale * e.scale, maxScale)
isAlreadyScaled = true
} else {
lastScale = Math.min(minScale * (lastScale * e.scale), maxScale)
}
}
updateCss(lastScale)
}
box.addEventListener('touchstart', e => {
if (e.touches.length === 2) {
handlePinch(e)
}
})
box.addEventListener('touchmove', e => {
if (e.touches.length === 2) {
handlePinch(e)
}
})
* {
box-sizing: border-box;
}
body {
padding: 0;
margin: 0;
}
.wrapper {
height: 400px;
width: 100%;
top: 0;
left: 0;
position: relative;
margin: 24px;
}
.scale-container {
height: 100%;
width: 100%;
position: absolute;
background: #eee;
display: flex;
align-items: center;
justify-content: center;
}
.box {
height: 200px;
width: 200px;
background: pink;
position: absolute;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
</head>
<body>
<div class="wrapper">
<div class="scale-container">
<div class="box"></div>
</div>
</div>
</body>
</html>
Related
My expected result is for the image to move within the container according to the calculations made on the x-axis mouse position. That is why Xratio ranges from -1 to 1.
I am not sure what is the readonly property here?
I used console.log(typeof Xratio) and got "number".
I also used console.log(typeof easeOutExpo) and saw "function".
Could this be a simple mistake I am not understanding with assigning?
const bxImg = document.querySelector('.bx-pic');
document.addEventListener('mousemove', (e) => {
const xRatio = (e.clientX / window.innerWidth) * 2 - 1;
console.log(typeof xRatio);
const xRatioEased = 0;
if (xRatio >= 0) {
xRatioEased = easeOutExpo(xRatio);
} else {
xRatioEased = easeOutExpo(-xRatio * -1)
}
bxImg.style.transform = `scale(1.2) translate3D(${-xRatioEased * 10}px, ${0}, ${0})`;
})
function easeOutExpo(n) {
if (n === 1) {
return 1;
} else {
return 1 - Math.pow(2, -10 * n);
}
}
console.log(typeof easeOutExpo);
html, body {
width: 100%;
height: 100%;
}
body {
background-color: #FDFDFD;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.bx-wp {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
width: 350px;
height: 350px;
outline: 30px solid royalblue;
}
.bx {
position: relative;
transform: scale(1.2);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="bx-wp">
<img src="https://images.unsplash.com/photo-1641113994135-a9f230b1f9b0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2670&q=80" alt="A Mountain" class="bx bx-pic">
</div>
<script src="script.js"></script>
</body>
</html>
You've declared xRatioEased as a const, so it can only be set on initialisation. Replace const xRatioEased with let xRatioEased and it should all work.
xRatioEased is a constant. Constants can be changed only on initialization. If you declare xRatioEased as a variable (with let or var), you can fix the issue.
If you want to know more about constants and when to use them, you can head over to this link
I have encountered some websites which has footer at the bottom and scroll actually happens when I scroll to the area above footer.
To automatically scroll those pages, but the problem with my code currently is it goes at the bottom of the page, where I directly reach footer and hence the scroll trigger which is present just above the footer does not gets triggered.
Is there any way to achieve the same?
This is what I have tried currently which I am executing from the console:
(function() {
var intervalObj = null;
var retry = 0;
var clickHandler = function() {
console.log("Clicked; stopping autoscroll");
clearInterval(intervalObj);
document.body.removeEventListener("click", clickHandler);
}
function scrollDown() {
var scrollHeight = document.body.scrollHeight,
scrollTop = document.body.scrollTop,
innerHeight = window.innerHeight,
difference = (scrollHeight - scrollTop) - innerHeight
if (difference > 0) {
window.scrollBy(0, difference);
if (retry > 0) {
retry = 0;
}
console.log("scrolling down more");
} else {
if (retry >= 3) {
console.log("reached bottom of page; stopping");
clearInterval(intervalObj);
document.body.removeEventListener("click", clickHandler);
} else {
console.log("[apparenty] hit bottom of page; retrying: " + (retry + 1));
retry++;
}
}
}
document.body.addEventListener("click", clickHandler);
intervalObj = setInterval(scrollDown, 1000);
})()
There are many websites that has this feature, to test the same one of the website which you can try is
https://www.zomato.com/bangalore/indiranagar-restaurants
Note : The question similar to this does not answer how to scroll at some mid point of page instead it takes me directly to the footer, so this is not a duplicate
Logic is to retain to the middle of the Scroller unless the page is completely loaded. We can tweak the code a little to achieve the last position of scroller. Try this:
var scrollHeight = 0,
newScrollHeight;
do {
window.scrollTo(0, document.body.scrollHeight / 2);
newScrollHeight = document.body.scrollHeight / 2;
if (newScrollHeight == scrollHeight) {
break;
} else {
scrollHeight = newScrollHeight;
}
} while (true);
Although Kumar Rishabh has already answered your question, I have another solution for this situation.
Set the domain to detect if the user scrolls to the domain.
The effect just like the website you povider . https://www.zomato.com/bangalore/indiranagar-restaurants
I do some simple example for you with pure Javascript.
Fragment core code:
// Here is domain to detect if user scroll into.
if (
triggerDomain.getBoundingClientRect().top < window.innerHeight &&
triggerDomain.getBoundingClientRect().bottom > 0
) {
if (getMore === false) {
getMore = true
// Do something you want here ....
console.info('got more !!')
Full code sample, check code snippet:
const rootElement = document.getElementById("rootDiv");
const triggerDomain = document.getElementById("triggerDomain");
let getMore = false;
function detectScrollIntoDomain() {
// Here is domain to detect if user scroll into.
if (
triggerDomain.getBoundingClientRect().top < window.innerHeight &&
triggerDomain.getBoundingClientRect().bottom > 0
) {
if (getMore === false) {
getMore = true;
// Do something you want here ....
console.info("got more !!");
setTimeout(() => {
let currentScrollTop = rootElement.scrollTop;
for (let i = 0; i < 15; i++) {
let r = Math.floor(Math.random() * 255);
let g = Math.floor(Math.random() * 255);
let b = Math.floor(Math.random() * 255);
const contentElement = document.getElementById("content");
const card = document.createElement("div");
card.className = "contentCard";
card.style.backgroundColor = `rgba(${r}, ${g}, ${b})`;
contentElement.appendChild(card);
}
rootElement.scrollTo(0, currentScrollTop);
// Don't forget to set flag to `false`.
getMore = false;
}, 200);
}
}
}
rootElement.addEventListener("scroll", detectScrollIntoDomain, {
passive: true
});
html,
body {
position: relative;
font-family: sans-serif;
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
h1,
h2 {
margin: 0;
color: aliceblue;
}
#rootDiv {
position: relative;
overflow: auto;
height: 100%;
width: 100%;
}
#header {
height: 200px;
background-color: rgb(112, 112, 112);
}
#content {
display: flex;
flex-wrap: wrap;
position: relative;
height: fit-content;
background-color: rgb(136, 136, 136);
}
#content div:first-child {
height: 600px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#triggerDomain {
position: absolute;
bottom: 0px;
outline: 1px dashed rgb(3, 25, 119);
height: 500px;
width: 100%;
background: repeating-linear-gradient(
135deg,
rgba(46, 45, 45, 0.3) 0,
rgba(46, 45, 45, 0.3) 10px,
rgba(136, 136, 136, 0.3) 10px,
rgba(136, 136, 136, 0.3) 20px
);
}
#footer {
height: 180%;
background-color: rgb(112, 112, 112);
}
.contentCard {
width: 180px;
height: 180px;
margin: 12px;
border-radius: 8px;
background-color: aquamarine;
}
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
<link rel="stylesheet" type="text/css" href="./src/styles.css"
</head>
<body>
<div id="rootDiv">
<div id="header">
<h1>Header</h1>
</div>
<div id="content">
<div>
<h2>Content</h2>
<h2>Scroll down to get more cards.</h2>
</div>
<div id="triggerDomain">
<h2>Trigger domain</h2>
</div>
</div>
<div id="footer">
<h2>Footer</h2>
</div>
</div>
<script src="src/index.js"></script>
</body>
</html>
Hope to help you !
I am making a vanilla js carousel. I have laid out basic previous and next functionality using js along with html and css.
Now I tried to use css-animations (keyframes) to do left and right slide-in/slide-out animations but the code became messy for me. So here I am asking that what minimal changes would be needed to get the same animation effects in this implementation ?
Will you go for pure JS based or pure CSS based or a mix to do the same ?
My goal is get proper animation with minimal code.
(function () {
let visibleIndex = 0;
let carousalImages = document.querySelectorAll(".carousal__image");
let totalImages = [...carousalImages].length;
function makeNextVisible() {
visibleIndex++;
if (visibleIndex > totalImages - 1) {
visibleIndex = 0;
}
resetVisible();
renderVisible();
}
function makePrevVisible() {
visibleIndex--;
if (visibleIndex < 0) {
visibleIndex = totalImages - 1;
}
resetVisible();
renderVisible();
}
function resetVisible() {
for (let index = 0; index < totalImages; index++) {
carousalImages[index].className = "carousal__image";
}
}
function renderVisible() {
carousalImages[visibleIndex].className = "carousal__image--visible";
}
function renderCarousel({ autoplay = false, autoplayTime = 1000 } = {}) {
if (autoplay) {
[...document.querySelectorAll("button")].forEach(
(btn) => (btn.style.display = "none")
);
setInterval(() => {
makeNextVisible();
}, autoplayTime);
} else renderVisible();
}
renderCarousel();
// Add {autoplay:true} as argument to above to autplay the carousel.
this.makeNextVisible = makeNextVisible;
this.makePrevVisible = makePrevVisible;
})();
.carousal {
display: flex;
align-items: center;
}
.carousal__wrapper {
width: 500px;
height: 400px;
}
.carousal__images {
display: flex;
overflow: hidden;
list-style-type: none;
padding: 0;
}
.carousal__image--visible {
position: relative;
}
.carousal__image {
display: none;
}
<div class='carousal'>
<div class='carousal__left'>
<button onclick='makePrevVisible()'>Left</button>
</div>
<section class='carousal__wrapper'>
<ul class='carousal__images'>
<li class='carousal__image'>
<img src='https://fastly.syfy.com/sites/syfy/files/styles/1200x680/public/2018/03/dragon-ball-super-goku-ultra-instinct-mastered-01.jpg?offset-x=0&offset-y=0' alt='UI Goku' / width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://www.theburnin.com/wp-content/uploads/2019/01/super-broly-3.png' alt='Broly Legendary' width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://lh3.googleusercontent.com/proxy/xjEVDYoZy8-CTtPZGsQCq2PW7I-1YM5_S5GPrAdlYL2i4SBoZC-zgtg2r3MqH85BubDZuR3AAW4Gp6Ue-B-T2Z1FkKW99SPHwAce5Q_unUpwtm4' alt='Vegeta Base' width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://am21.mediaite.com/tms/cnt/uploads/2018/09/GohanSS2.jpg' alt='Gohan SS2' width='500' height='400'/>
</li>
</ul>
</section>
<div class='carousal__right'>
<button onclick='makeNextVisible()'>Right</button>
</div>
</div>
Updated codepen with feedback from the below answers and minor additional functionalities = https://codepen.io/lapstjup/pen/RwoRWVe
I think the trick is pretty simple. ;)
You should not move one or two images at the same time. Instead you should move ALL images at once.
Let's start with the CSS:
.carousal {
position: relative;
display: block;
}
.carousal__wrapper {
width: 500px;
height: 400px;
position: relative;
display: block;
overflow: hidden;
margin: 0;
padding: 0;
}
.carousal__wrapper,
.carousal__images {
transform: translate3d(0, 0, 0);
}
.carousal__images {
position: relative;
top: 0;
left: 0;
display: block;
margin-left: auto;
margin-right: auto;
}
.carousal__image {
float: left;
height: 100%;
min-height: 1px;
}
2nd step would be to calculate the maximum width for .carousal__images. For example in your case 4 * 500px makes 2000px. This value must be added to your carousal__images as part of the style attribute style="width: 2000px".
3rd step would be to calculate the next animation point and using transform: translate3d. We start at 0 and want the next slide which means that we have slide to the left. We also know the width of one slide. So the result would be -500px which also has to be added the style attribute of carousal__images => style="width: 2000px; transform: translate3d(-500px, 0px, 0px);"
That's it.
Link to my CodePen: Codepen for Basic Carousel with Autoplay
Try this. First stack all the images next to each other in a div and only show a single image at a time by setting overflow property to hidden for the div. Next, add event listeners to the buttons. When a bottom is clicked, the div containing the images is translated by -{size of an image} * {image number} on the x axis. For smooth animation, add transition: all 0.5s ease-in-out; to the div.
When someone clicks left arrow on the first image, the slide should display the last image. So for that counter is set to {number of images} - 1 and image is translated to left size * counter px.
For every click on the right arrow, the counter is incremented by 1 and slide is moved left. For every click on the left arrow, the counter is decremented by 1.
Slide.style.transform = "translateX(" + (-size * counter) + "px)"; this is the condition which is deciding how much the slide should be translated.
const PreviousButton = document.querySelector(".Previous-Button");
const NextButton = document.querySelector(".Next-Button");
const Images = document.querySelectorAll("img");
const Slide = document.querySelector(".Images");
const size = Slide.clientWidth;
var counter = 0;
// Arrow Click Events
PreviousButton.addEventListener("click", Previous);
NextButton.addEventListener("click", Next);
function Previous() {
counter--;
if (counter < 0) {
counter = Images.length - 1;
}
Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}
function Next() {
counter++;
if (counter >= Images.length) {
counter = 0;
}
Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}
* {
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
.Container {
width: 60%;
margin: 0px auto;
margin-top: 90px;
overflow: hidden;
position: relative;
}
.Container .Images img {
width: 100%;
}
.Images {
transition: all 0.5s ease-in-out;
}
.Container .Previous-Button {
position: absolute;
background: transparent;
border: 0px;
outline: 0px;
top: 50%;
left: 20px;
transform: translateY(-50%);
filter: invert(80%);
z-index: 1;
}
.Container .Next-Button {
position: absolute;
background: transparent;
border: 0px;
outline: 0px;
top: 50%;
right: 20px;
transform: translateY(-50%);
filter: invert(80%);
z-index: 1;
}
.Container .Images {
display: flex;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css2?family=Cabin&family=Poppins&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<title>Carousel</title>
</head>
<body>
<div class="Container">
<button class="Previous-Button">
<svg style = "transform: rotate(180deg);" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
</button>
<button class="Next-Button">
<svg xmlns="http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
</button>
<div class="Images">
<img src="https://source.unsplash.com/1280x720/?nature">
<img src="https://source.unsplash.com/1280x720/?water">
<img src="https://source.unsplash.com/1280x720/?rock">
<img src="https://source.unsplash.com/1280x720/?abstract">
<img src="https://source.unsplash.com/1280x720/?nature">
<img src="https://source.unsplash.com/1280x720/?trees">
<img src="https://source.unsplash.com/1280x720/?human">
<img src="https://source.unsplash.com/1280x720/?tech">
</div>
</div>
<script src="main.js"></script>
</body>
</html>
I'm trying to make slide-show website using intersectionObserver. Basically, i grep elements and listen for intersection event, then i want to set window.srollTo to element's offsetTop. I have tried window.scrollTo(0, 10), elem.scrollIntoView(), window.scrollBy(), but nothing is working at all.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
position: ablolute;
-ms-overflow-style: none;
display: block;
height: 100vh;
width: 100%;
}
body::-webkit-scrollbar {
width: 0 !important;
}
.page {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="page">one</div>
<div class="page">two</div>
<div class="page">three</div>
<div class="page">four</div>
<script>
const pages = document.querySelectorAll('.page');
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting == true) {
console.log(entry.target.offsetTop);
entry.target.scrollIntoView(top);
}
});
},
{threshold: 0.01},
);
pages.forEach(page => {
observer.observe(page);
});
</script>
</body>
</html>
Firstly scrollIntoView takes a Boolean or an options Object. I don't know what top is supposed to be since it's not in your code, but it's not correct.
Your scroll event is firing constantly and overriding your scrollIntoView. In order to stop this you can set the container's overflow property so that it no longer allows scrolling, disabling the event, and then re-enable it with a timer just before calling scrollIntoView.
entries.forEach(entry => {
if (entry.isIntersecting == true) {
console.log(entry.target.offsetTop);
document.body.setAttribute('style','overflow:hidden;');
setTimeout(function(){
document.body.setAttribute('style','overflow:visible;');
entry.target.scrollIntoView(true);
}, 250);
}
Example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
position: absolute;
-ms-overflow-style: none;
display: block;
height: 100vh;
width: 100%;
}
body::-webkit-scrollbar {
width: 0 !important;
}
.page {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="page">one</div>
<div class="page">two</div>
<div class="page">three</div>
<div class="page">four</div>
<script>
const pages = document.querySelectorAll('.page');
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting == true) {
console.log(entry.target.offsetTop);
document.body.setAttribute('style','overflow:hidden;');
setTimeout(function(){
document.body.setAttribute('style','overflow:visible;');
entry.target.scrollIntoView(true);
}, 250);
}
});
},
{threshold: 0.10},
);
pages.forEach(page => {
observer.observe(page);
});
</script>
</body>
</html>
Note There are likely better ways to do this without a timer - for instance a flag within a closure, etc, but this should give you an idea of what's causing the issue and how to get around it.
i need a draggable div not to go outside of second div frame, so far i managed to make a "collision" basically returning true or false if draggable div is inside the frame of other div. So the thing now is that i cant get this to work, i was trying to get it to a x = 90(example) when it hits the frame and few more examples , but i just can't get this to work. The draggable div doesn't want to go back to position.
Here is a JSFiddle: https://jsfiddle.net/kojaa/x80wL1mj/2/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="style.css">
<title>Catch a ball</title>
</head>
<body>
<div class="catcherMovableArea" id="catcherMovableArea">
<div id="catcher" class="catcher"></div>
</div>
<script src="script.js"></script>
</body>
</html>
.catcherMovableArea {
position: absolute;
border: 1px solid black;
width: 1900px;
top: 90%;
height: 50px;
}
.catcher {
position: absolute;
width: 250px;
height: 30px;
border-radius: 10px;
background-color: black;
top: 20%;
}
let catcher = $("#catcher");
$(document).mousemove(function(e) {
catcher.position({
my: "left-50% bottom+50%",
of: e,
collision: "fit"
});
let catcherOffset = $(catcher).offset();
let CxPos = catcherOffset.left;
let CyPos = catcherOffset.top;
let catcherYInMovableArea, catcherXInMovableArea = true;
while(!isCatcherYinMovableArea(CyPos)){
catcherYInMovableArea = false;
break;
}
while(!isCatcherXinMovableArea(CxPos)){
catcherXInMovableArea = false;
break;
}
});
function isCatcherYinMovableArea(ypos){
if(ypos < 849.5999755859375 || ypos > 870.5999755859375) {
return false;
} else {
return true;
}
}
function isCatcherXinMovableArea(xpos){
if(xpos < 8 || xpos > 1655 ) {
return false;
} else {
return true;
}
}
By default, the collision option will prevent the element from being placed outside of the window. You want to prevent it from moving outside of a specific element.
To do this, use the within option to select which element should be used for containment.
Example:
let draggable = $("#draggable");
$(document).mousemove(function(e) {
draggable.position({
my: "left-50% bottom+50%",
of: e,
collision: "fit",
within: "#container"
});
});
#container { width: 200px; height: 100px; border-style: solid }
#draggable { width: 50px; height: 50px; background-color: black }
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="https://code.jquery.com/ui/1.12.0-rc.1/jquery-ui.js"></script>
<div id="container">
<div id="draggable"></div>
</div>