Move text only when scrolling - Framework? - javascript

Hello, everyone!
Is there a framework or something else that makes it easy to move text (to the left or right) when the user scrolls (for example near the footer)? So a pure scroll animation.
Thank you in advance!
I tried Scrollmagic, but i cant handle it. :/

You can try gsap , however if you want to make it without a framework using scroll event you can try :
const first = document.getElementById("first")
const second = document.getElementById("second")
const third = document.getElementById("third")
const container = document.getElementById("container")
const rect = container.getBoundingClientRect()
const animate = (element,position) => {
element.style.transform = `translateX(${position}px)`
}
document.addEventListener('scroll', function(e) {
lastKnownScrollPosition = window.scrollY;
window.requestAnimationFrame(function() {
animate(first,lastKnownScrollPosition*.2)
animate(second,lastKnownScrollPosition*-.2)
animate(third,lastKnownScrollPosition*.2)
});
});
body{
height: 200vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#container{
max-width: 100vw;
overflow: hidden;
}
h1{
transition: transform 0.2s linear;
}
<body>
<div id="container">
<h1 id="first">
this is a text this is a text this is a text
</h1>
<h1 id="second">
this is a text this is a text this is a text
</h1>
<h1 id="third">
this is a text this is a text this is a text
</h1>
</div>
</body>

This is a simple exemple to demonstrate the idea using jquery. gasp like #Chakib said before is a good solution.
function isVisible(el, plus, wrapper = window ){
var winFromTop = jQuery(wrapper).scrollTop();
var currentPosition = winFromTop + jQuery(wrapper).height();
var el = jQuery(`${el}`);
var elFromTop = el.length ? jQuery(el[0]).offset().top : jQuery(el).offset().top;
return (currentPosition + plus) > elFromTop
}
jQuery(window).scroll(function(e) {
var idElement = '.anime';
const visible = isVisible(idElement, -20)
if(visible){
console.log('=> ', idElement)
jQuery(idElement).addClass('resetX')
}
else {
jQuery(idElement).removeClass('resetX')
}
});
body{
overflow-x: hidden
}
.container{
border: solid 1px red;
max-width: 800px;
margin: auto auto;
padding: 5px;
}
.above-the-fold{
background: linear-gradient(180deg, rgba(163,196,243,1) 27%, rgba(142,236,245,1) 100%);
height: calc(100vh + 300px);
margin-bottom: 30px;
}
.boxes{
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.box{
margin: 10px;
height: 200px;
flex: 0 0 calc(50% - 30px);
background-color: #98f5e1;
}
.text{
font-size: 18px;
margin: 10px;
}
.anime{
transition: all .7s ease-in-out;
}
.left{
transform: translateX(220px);
}
.right{
transform: translateX(-220px);
}
.resetX{
transform: translateX(0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="above-the-fold"></div>
<div class="boxes">
<div class="box anime right"></div>
<div class="box anime left"></div>
<div class="text anime right">Loren Ipsum</div>
<div class="text anime left">Loren Ipsum</div>
</div>
</div>

Related

Convert vertical scrolling Website to a functional horizontal scrolling website

The current code is a responsive vertical webpage with a vertical navigation
I want to convert It to a responsive horizontal webpage with left-right arrow control.
Instances
When scrolling up or down with the mouse, the website should go left when scrolling up and right when scrolling down.
When right or left arrow keys are pressed the website should scroll right or left depending on the key pressed.
My HTML code
<body>
<div class="section" id="home" data-label="Home">Home</div>
<div class="section" id="about" data-label="About Me">About</div>
<div class="section" id="contact" data-label="Say Hi">Contact</div>
<script>
function activateNavigation() {
const sections = document.querySelectorAll(".section");
const navContainer = document.createElement("nav");
const navItems = Array.from(sections).map((section) => {
return `
<div class="nav-item" data-for-section="${section.id}">
<span class="nav-label">${section.dataset.label}</span>
</div>
`;
});
navContainer.classList.add("nav");
navContainer.innerHTML = navItems.join("");
const observer = new IntersectionObserver(
(entries) => {
document.querySelectorAll(".nav-link").forEach((navLink) => {
navLink.classList.remove("nav-link-selected");
});
const visibleSection = entries.filter((entry) => entry.isIntersecting)[0];
document
.querySelector(
`.nav-item[data-for-section="${visibleSection.target.id}"] .nav-link`
)
.classList.add("nav-link-selected");
},
{ threshold: 0.5 }
);
sections.forEach((section) => observer.observe(section));
document.body.appendChild(navContainer);
}
activateNavigation();
</script>
</body>
My CSS
.section{
height: 100vh;
.nav{
--nav-gap : 15px;
padding: var(--nav-gap);
position: fixed;
right: 0;
top:50%;
transform: translateY(-50%);
}
.nav-item{
align-items: center;
display: flex;
flex-direction: row-reverse;
margin-bottom: var(--nav-gap);
}
.nav-link:hover ~ .nav-label{
opacity: 1;
}
.nav-label{
color: black;
font-weight: bold;
opacity: 0;
transition: opacity 0.1s;
}
.nav-link{
background: rgba(0,0,0,0.5);
border-radius: 50%;
height: var(--nav-gap);
margin-left: var(--nav-gap);
width: var(--nav-gap);
transition: transform 0.3s;
}
.nav-link-selected{
background: #000000;
transform: scale(1.4);
}
The idea is to make a container div which will contain all the sections, and then make it display flex to handle sections as rows not columns.
Make sure to that the width and height of the container match the body's.
and then disable overflow-x on the container.
Now all we have to do is to get the current scroll Y position and transform it into a X one.
Since the browser does the job for us, we don't really need to calculate anything... just get paste the same Y position as an X one and it will work like a charm.
There is the script and explanations... All you have to do next is to challenge yourself to get the proper height of the website and use it as min-height for the body
A quick hint on your challenge: The height you'll need could be equal to the width of your container.
Good luck!
Example with your layout: jsfiddle link
const container = document.querySelector(".container")
window.addEventListener("scroll", horizontalScroll)
window.addEventListener("keydown", horizontalScroll)
function horizontalScroll(e, keyboadScrollingSpeed=30) {
let y = window.scrollY || window.pageYOffset
if( e.type == "keydown" ) {
if( (e.key == 'ArrowLeft' || e.key == 'ArrowRight') ) {
const direction = e.key == 'ArrowLeft' ? -1 : 1
y += keyboadScrollingSpeed * direction
window.scrollTo({
top: y
})
}
else e.preventDefault()
}
container.scrollTo({
left: y,
})
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 300vh;
}
.container {
background-color: yellow;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
position: sticky;
top: 0;
left: 0;
width: 100vw;
overflow-x: hidden;
}
.section {
display: block;
flex: 1;
max-width: 100vw;
min-width: 100vw;
height: 100vh;
background-color: green;
}
<div class="container">
<div class="section">
<div class="content">
<h1>Section 1</h1>
<p>This is some text</p>
</div>
</div>
<div class="section">
<div class="content">
<h1>Section 2</h1>
<p>This is some text</p>
</div>
</div>
</div>

How can I change parent of an element with transform translate animation?

I want to move an element from one parent to another parent. Here I wanna apply CSS transform animation.
function abc() {
let child = document.querySelector("#child");
let parent = document.querySelector("#div_b");
parent.appendChild(child);
}
<div id="div_a" style="height:30px; width:30px; background-color:yellow;">
<div id="child" class="new-box">
<div style="width: 20px; height: 20px; background-color: green;"></div>
</div>
</div>
<div id="div_b" style="height:30px; width:30px; background-color:red;">
</div>
<button onclick="abc()">move</button>
You can find out where the element is currently, move it to its new parent and find out where it is now, put it back in its original parent and set an animation for it to translate to its new position.
When the animation has completed then you put the element into its new position (ie into its new parent).
function abc() {
const child = document.querySelector("#child");
const parent = document.querySelector("#div_b");
const parentOriginal = document.querySelector("#div_a");
parentOriginal.appendChild(child); //put back to where it was
const x0 = child.getBoundingClientRect().left;
const y0 = child.getBoundingClientRect().top;
parent.appendChild(child);
const x1 = child.getBoundingClientRect().left;
const y1 = child.getBoundingClientRect().top;
parentOriginal.appendChild(child);
child.style.setProperty('--dx', (x1 - x0) + 'px');
child.style.setProperty('--dy', (y1 - y0) + 'px');
child.addEventListener('animationend', function() {
parent.appendChild(child);
child.classList.remove('move');
});
child.classList.add('move');
}
.move {
animation: move 2s linear 1;
}
#keyframes move {
0% {
transform: translateX(0) translateY(0);
}
100% {
transform: translateX(var(--dx)) translateY(var(--dy));
}
}
<div id="div_a" style="height:30px; width:30px; background-color:yellow;">
<div id="child" class="new-box">
<div style="width: 20px; height: 20px; background-color: green;"></div>
</div>
</div>
<div id="div_b" style="height:30px; width:30px; background-color:red;">
</div>
<button onclick="abc()">move</button>
In addition to A Haworth's excellent answer, you could further iterate on this by using the Web Animations API. This API uses the same fundamentals as CSS Animations, but allows you to perform the actions through JavaScript.
Like Haworth, get the first position of the #child element. Then move it to it's destination. Measure the position of the #child again. Subtract the last position from the first position. The result of this subtraction is the difference between the two points, otherwise known as the delta.
Use the delta to translate the #child to it's previous first and then animate it to the last known position.
const moveButton = document.querySelector('#move');
const origin = document.querySelector('#div_a');
const target = document.querySelector('#div_b');
const child = document.querySelector("#child");
moveButton.addEventListener('click', () => {
const { left: x0, top: y0 } = child.getBoundingClientRect();
target.append(child);
const { left: x1, top: y1 } = child.getBoundingClientRect();
const dx = x0 - x1;
const dy = y0 - y1;
if (dx === 0 && dy === 0) {
return;
}
const transformFrom = `translate3d(${dx}px, ${dy}px, 0)`;
const transformTo = `translate3d(0, 0, 0)`;
const animation = child.animate([
{ transform: transformFrom },
{ transform: transformTo },
], {
duration: 2000,
easing: 'linear',
});
});
<div id="div_a" style="height:30px; width:30px; background-color:yellow;">
<div id="child" class="new-box">
<div style="width: 20px; height: 20px; background-color: green;"></div>
</div>
</div>
<div id="div_b" style="height:30px; width:30px; background-color:red;">
</div>
<button id="move">move</button>
I think this will solve your problem.
const firstContainer = document.querySelector('.first-container');
const secondContainer = document.querySelector('.second-container');
const box = document.querySelector('.box');
box.addEventListener('click', () => {
box.classList.add('move');
setTimeout(() => {
firstContainer.removeChild(box);
box.classList.remove('move');
secondContainer.appendChild(box);
},1000);
},{once:true})
*,
*::before,
*::after {
box-sizing: border-box;
}
body{
min-height: 100vh;
overflow: hidden;
display: grid;
place-content: center;
margin:0;
background-color: bisque;
}
.container{
background-color: aquamarine;
display: flex;
flex-direction: row;
}
.first-container,
.second-container{
width: 20rem;
height: 20rem;
background-color: aquamarine;
border: 1px solid black;
display: grid;
place-content: center;
}
.box{
width: 10rem;
height: 10rem;
background-color: brown;
border-radius: 100%;
display: grid;
place-content: center;
}
.box p{
color:white;
font-size: 2rem;
font-weight: 500;
width: 100%;
}
.move {
animation: movement 1s forwards;
}
#keyframes movement {
100%{
transform: translateX(200%);
}
}
<div class="container">
<div class="first-container">
<div class="box">
<p>Click me</p>
</div>
</div>
<div class="second-container"></div>
</div>

Horizontal scroll areas with buttons and gradients

This is my code so far:
// Show and hide gradients
$(document).ready(function() {
$(".scroll-area").each(function(index) {
if ($(this)[0].scrollWidth <= $(this)[0].clientWidth) {
$(this).closest(".container").find(".left").css("display", "none");
$(this).closest(".container").find(".right").css("display", "none");
} else {
$(this).scroll(function() {
if ($(this)[0].scrollWidth > $(this)[0].clientWidth) {
if ($(this).scrollLeft() > 0) {
$(this).closest(".container").find(".left").css("display", "block");
}
if ($(this).scrollLeft() == 0) {
$(this).closest(".container").find(".left").css("display", "none");
}
var fullWidth = $(this)[0].scrollWidth - $(this)[0].offsetWidth - 1;
if ($(this).scrollLeft() >= fullWidth) {
$(this).closest(".container").find(".right").css("display", "none");
}
if ($(this).scrollLeft() < fullWidth) {
$(this).closest(".container").find(".right").css("display", "block");
}
}
});
}
});
});
// Scroll buttons
let interval;
$('.scroll-btn').on('mousedown', ({
target
}) => {
const type = $(target).attr('id');
interval = setInterval(() => {
var x = $('#a').scrollLeft();
$('#a').scrollLeft(type === 'left-arrow' ? x - 10 : x + 10);
}, 50);
});
$('.scroll-btn').on('mouseup', () => clearInterval(interval));
* {
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 16px;
}
.container {
width: 550px;
height: 80px;
background-color: grey;
position: relative;
margin-bottom: 20px;
}
.scroll-area {
white-space: nowrap;
overflow-x: auto;
height: 100%;
}
.left,
.right {
width: 50px;
height: 100%;
position: absolute;
pointer-events: none;
top: 0;
}
.left {
background: linear-gradient(90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 0;
display: none;
}
.right {
background: linear-gradient(-90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
right: 0;
}
.arrow {
display: block;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 15px;
cursor: pointer;
}
.left-arrow {
left: 0;
}
.right-arrow {
right: 0;
}
.left-arrow div,
.right-arrow div {
font-size: 40px;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<div class="container">
<div id="x" class="left"></div>
<div class="right"></div>
<div class="arrow left-arrow">
<div class="scroll-btn" id="left-arrow">
<</div>
</div>
<div class="arrow right-arrow">
<div class="scroll-btn" id="right-arrow">></div>
</div>
<div id="a" class="scroll-area">
<div class="text">Scroll to right. The gradients and arrows should appear and disappear based on the scroll position. It should work with more than one container. Lorem ipsum.</div>
</div>
</div>
The needs are:
The arrows should appear and disappear in the same way like the gradients.
If there is not enough text to cause a scrollable area, there should be no gradient and now arrow.
There should be more than one container in the end.
Can somebody help me to do that? I would be super thankful!
You can put your arrows inside the left/right gradient divs. That way they will show/hide same way as the gradients.
EDIT
I cleaned up the code a bit since the original answer was kinda messy. (or 'weird' as mstephen19 put it :)).
// Show gradient and left/right arrows only if scrollable
$(".scroll-area").each((i, el) => {
$(el).parent().find(".right")[el.scrollWidth > el.clientWidth ? "show" : "hide"]();
});
// Show/hide gradient and arrows on scroll
$('.scroll-area').scroll((e) => {
const fullWidth = $(e.target)[0].scrollWidth - $(e.target)[0].offsetWidth - 1;
const left = $(e.target).scrollLeft()
$(e.target).parent().find(".left, .left-arrow")[left > 0 ? "show" : "hide"]();
$(e.target).parent().find(".right, .right-arrow")[left < fullWidth ? "show" : "hide"]();
});
// Scroll on left/right arrow mouse down
let intervalId;
$(".left-arrow, .right-arrow").on("mousedown", (e) => {
const scroll = $(e.target).closest(".container").find(".scroll-area");
intervalId = setInterval(() => {
const left = scroll.scrollLeft();
scroll.scrollLeft(e.target.classList.contains("left-arrow") ? left - 10 : left + 10);
}, 50);
}).on("mouseup mouseleave", () => {
clearInterval(intervalId);
});
* {
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 16px;
}
.container {
width: 550px;
height: 80px;
background-color: grey;
position: relative;
margin-bottom: 20px;
margin-left: 20px;
}
.scroll-area {
white-space: nowrap;
overflow-x: auto;
height: 100%;
}
.left,
.right {
width: 50px;
height: 100%;
position: absolute;
top: 0;
}
.left {
background: linear-gradient(90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 0;
display: none;
}
.right {
background: linear-gradient(-90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
right: 0;
text-align: right;
}
.left-arrow,
.right-arrow {
margin: 0 10px;
position: absolute;
top: 50%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
cursor: pointer;
user-select: none;
font-size: 40px
}
.left-arrow {
display: none;
left: -25px;
}
.right-arrow {
right: -25px;
}
<html>
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="left-arrow"><</div>
<div class="right-arrow">></div>
<div class="scroll-area">
<div class="text">Scroll to right. The gradients and arrows should appear and disappear based on the scroll position. It should work with more than one container. Lorem ipsum.</div>
</div>
</div>
<div class="container">
<div class="left"><span class="left-arrow"><</span></div>
<div class="right"><span class="right-arrow">></span></div>
<div class="scroll-area">
<div class="text">No scroll.</div>
</div>
</div>
</body>
</html>
Some things about your code:
Your original code would not work with multiple containers, because you had a hardcoded #a ID in the interval code. You should really only have IDs on one element ideally, anyways (they're unique identifiers, while classes can be placed on multiple elements). The .scroll-area element should be found based on the target clicked.
You should combine your gradient and arrow elements into one element. By that, I mean making the div in which the arrow lives should be a child of the gradient div. Why manage them both separately?
Use class adding/removing/toggling instead of directly setting the CSS. Remember - when you find yourself writing the same code multiple times, it usually means there is a way to condense it down and make your code more dry and easier to understand + read.
Don't use the literal < and > symbols, as it can confuse some browsers. Use < and > instead.
Rather than toggling display to none and block, it's better to use visibility in this specific case. In my example, we use opacity for a fun fading effect.
Don't forget to listen for both mouseup mouseout events :)
Here is the working solution. I've refactored the code a bit:
let interval;
$('.arrow').on('mousedown', ({ target }) => {
const type = target.classList[1];
const scrollArea = $(target).parent().find('.scroll-area');
interval = setInterval(() => {
const prev = scrollArea.scrollLeft();
scrollArea.scrollLeft(type === 'left-arrow' ? prev - 10 : prev + 10);
}, 50);
});
$('.arrow').on('mouseup mouseout', () => clearInterval(interval));
$('.scroll-area').on('scroll', ({ target }) => {
const left = $(target).parent().find('.left-arrow');
const right = $(target).parent().find('.right-arrow');
const scroll = $(target).scrollLeft();
const fullWidth = $(target)[0].scrollWidth - $(target)[0].offsetWidth;
if (scroll === 0) left.addClass('hide');
else left.removeClass('hide');
if (scroll > fullWidth) right.addClass('hide');
else right.removeClass('hide');
});
.container {
width: 550px;
height: 80px;
background: grey;
position: relative;
}
.right-arrow,
.left-arrow {
height: 100%;
width: 50px;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
cursor: pointer;
transition: all 0.2s linear;
}
.scroll-area {
white-space: nowrap;
overflow-x: scroll;
height: 100%;
}
.right-arrow {
background: linear-gradient(-90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 500px;
}
.left-arrow {
background: linear-gradient(90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 0px;
}
.scroll-btn {
pointer-events: none;
}
.hide {
opacity: 0;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<div class="container">
<div class="arrow left-arrow">
<div class="scroll-btn" id="left-arrow"><</div>
</div>
<div class="arrow right-arrow">
<div class="scroll-btn" id="right-arrow">></div>
</div>
<div class="scroll-area">
<div class="text">
Scroll to right. The gradients and arrows should appear and disappear based on the scroll position. It should work with more than one
container. Lorem ipsum.
</div>
</div>
</div>
PS: If you don't like the fade effect, remove the transition: all 0.2s linear; part of the CSS, and switch .hide's opacity: 0 to visibility: hidden.

How can I trigger on mouse movement Variable font in different section?

I was looking to trigger different sections with a variable font based on my mouse movement.
For the first section, everything looks great, but when I tried to trigger the second section, it does not work as I expected since is connected to the first one I guess.
I would need to make the section working independently and in the correct way (to have an idea see section one how react in debug mode)
I was wondering what I have to modify in my Javascript code to make my snippet work with all the sections I want, working independently with their respective variable font interaction. Any ideas?
$('.square').on('mousemove', function(e) {
var x = e.pageX - $(this).offset().left;
var y = e.pageY;
var $tlSquare = $('.division--top.division--left');
var $trSquare = $('.division--top.division--right');
var $blSquare = $('.division--bottom.division--left');
var $brSquare = $('.division--bottom.division--right');
var squareWidth = $(this).width(),
squareHeight = $(this).height();
$tlSquare.width(x).height(y);
$trSquare.width(squareWidth - x).height(y);
$blSquare.width(x).height(squareHeight - y);
$brSquare.width(squareWidth - x).height(squareHeight - y);
stretchLetter(false);
});
stretchLetter(false);
$('.square').on('mouseleave', function() {
$('.division').width('50%').height('50%');
$('.letter').css('transform', '');
stretchLetter(false);
});
function stretchLetter(animation) {
$('.letter').each(function() {
var parentWidth = $(this).parent().width();
var parentHeight = $(this).parent().height();
var thisWidth = $(this).width();
var thisHeight = $(this).height();
var widthPercent = parentWidth / thisWidth;
var heightPercent = parentHeight / thisHeight;
var timing = animation == true ? .5 : 0;
TweenMax.to($(this), timing, {
scaleX: widthPercent,
scaleY: heightPercent
})
//$(this).css('transform', 'scalex('+ widthPercent +') scaley('+ heightPercent +')');
});
}
body,
html {
margin: 0;
padding: 0;
font-weight: bold;
font-family: helvetica;
}
section {
height: 200px;
background: blue;
color: white;
font-size: 28px;
}
.wrapper {
display: flex;
justify-content: center;
/*justify-content: flex-end;*/
width: 100%;
height: 100vh;
//background-color: blue;
overflow: hidden;
}
.square {
display: flex;
flex-wrap: wrap;
width: 100vh;
height: 100vh;
background-color: black;
}
.square-2 {
display: flex;
flex-wrap: wrap;
width: 100vh;
height: 100vh;
background-color: yellow;
}
.division {
//display: flex;
//align-items: center;
//justify-content: center;
width: 50%;
height: 50%;
//background-color: red;
//border: 1px solid white;
box-sizing: border-box;
}
.letter {
cursor: -webkit-grab;
cursor: grab;
}
.letter {
display: inline-block;
font-size: 50vh;
margin: 0;
padding: 0;
line-height: .8;
transform-origin: top left;
color: white;
}
/* .division:nth-child(1){
background-color: blue;
}
.division:nth-child(2){
background-color: red;
}
.division:nth-child(3){
background-color: green;
}
.division:nth-child(4){
background-color: orange;
} */
.circle {
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid white;
background-color: blue;
box-sizing: border-box;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>SECTION-01</section>
<main>
<div class="wrapper">
<div class="square">
<div class="division division--top division--left">
<div class="letter">L</div>
</div>
<div class="division division--top division--right">
<div class="letter">A</div>
</div>
<div class="division division--bottom division--left">
<div class="letter">S</div>
</div>
<div class="division division--bottom division--right">
<div class="letter">T</div>
</div>
</div>
</main>
<section>SECTION-02</section>
<div class="wrapper">
<div class="square">
<div class="division division--top division--left">
<div class="letter">F</div>
</div>
<div class="division division--top division--right">
<div class="letter">A</div>
</div>
<div class="division division--bottom division--left">
<div class="letter">S</div>
</div>
<div class="division division--bottom division--right">
<div class="letter">T</div>
</div>
</div>
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script>
https://jsfiddle.net/CAT999/ohaf61qp/5/
See working FIDDLE
You had to change the y variable because you were calculating with the offset top of the mouse position inn the document. This is always bigger than the element, so you have to extract the offset top of the element you were scrolling on, to get the right value.
var y = e.pageY - $(this).offset().top;

How to keep user's scrolling place when resizing div

I wanted to do a cool menu effect for a website I'm working on. I'm having a div act as the the section for the main content. When the user opens the menu, the main content div will resize and move out of the way, revealing the menu. However, when I do this with the code I have written, it always loses my scrolling place on the page. Is there any way to keep my place on the page when it shrinks and also when it expands back again? Below is what I have. Thank you in advance!
function shrinkPage() {
var element = document.getElementById("mock-body");
element.classList.toggle("mock-body-on-burger");
var z = document.getElementById("mock-body-container");
z.classList.toggle("mock-body-container-on-burger");
var x = document.getElementById("body");
x.classList.toggle("body-on-burger");
};
body {
margin: 0;
background:#000;
}
.body-on-burger {
max-width: 100%;
overflow-x:hidden;
}
.mock-body-container{
height:100vh;
}
.mock-body-container-on-burger {
height:100vh;
transform: scale(0.4) translate(130%);
overflow: hidden;
}
.mock-body-size-change{
overflow: scroll;
}
.mock-body {
position:relative;
background: #fff;
margin-left: 50px;
}
.container {
position: fixed;
height:50px;
width:50px;
cursor: pointer;
}
.container #icon {
width: 16px;
height: 8px;
position: relative;
margin: 0px auto 0;
top: 40%;
}
.container #icon .bars {
height: 1px;
background: #fff;
}
.myDiv {
height:500px;
}
.one {
background:red;
}
.two {
background:green;
}
.three {
background:blue;
}
<body id="body">
<div class="menu-activator" onclick="shrinkPage()">
<div class="container usd">
<div id="icon">
<div class="bars first"></div>
</div>
</div>
</div>
<div id="mock-body-container" class="mock-body-container">
<div id="mock-body" class="mock-body">
<div class="myDiv one"></div>
<div class="myDiv two"></div>
<div class="myDiv three"></div>
</div>
</div>
</body>
Please take a look at the snippet below. Notice how the overflow property is used.
You have to scroll mock-body-container to keep its scrolling position.
You're scrolling body instead, so when you scale mock-body-container there is nothing to scroll in body and you loose the scrolling position.
function shrinkPage() {
var element = document.getElementById("mock-body");
element.classList.toggle("mock-body-on-burger");
var z = document.getElementById("mock-body-container");
z.classList.toggle("mock-body-container-on-burger");
var x = document.getElementById("body");
x.classList.toggle("body-on-burger");
};
body {
margin: 0;
background:#000;
}
.body-on-burger {
max-width: 100%;
overflow-x:hidden;
}
.mock-body-container{
height:100vh;
overflow:auto;
}
.mock-body-container-on-burger {
height:100vh;
transform: scale(0.4) translate(130%);
}
.mock-body-size-change{
overflow: scroll;
}
.mock-body {
position:relative;
background: #fff;
margin-left: 50px;
}
.container {
position: fixed;
height:50px;
width:50px;
cursor: pointer;
}
.container #icon {
width: 16px;
height: 8px;
position: relative;
margin: 0px auto 0;
top: 40%;
}
.container #icon .bars {
height: 1px;
background: #fff;
}
.myDiv {
height:500px;
}
.one {
background:red;
}
.two {
background:green;
}
.three {
background:blue;
}
<body id="body">
<div class="menu-activator" onclick="shrinkPage()">
<div class="container usd">
<div id="icon">
<div class="bars first"></div>
</div>
</div>
</div>
<div id="mock-body-container" class="mock-body-container">
<div id="mock-body" class="mock-body">
<div class="myDiv one"></div>
<div class="myDiv two"></div>
<div class="myDiv three"></div>
</div>
</div>
</body>
Once you know the element that was in focus it should be relatively easy. If you need to find which element was last in focus, you can do that with a scroll function. If you need this as well let me know and I will update my answer.
If you know that #mock-body is the last element in focus, just scroll back to it after the resize.
In this example I've used jQuery as it makes this interaction easier, but this can be done (albeit more verbosely) with vanilla JS as well.
$('html, body').animate({
scrollTop: $('#mock-body').offset().top
}, 0); // If you want the animation to be smoother you can increase 0 to a higher number
A simple way to do it is to remember the position of the document scroll and reapply it when you getting back to "normal" view:
let savedScroll;
function shrinkPage() {
let _s = (el) => document.querySelector(el),
s_ = (d) => !d.classList.contains('body-on-burger'),
x = _s('#body'),
element = _s('#mock-body'),
z = _s('#mock-body-container');
if (s_(x)) {
savedScroll = document.documentElement.scrollTop;
}
element.classList.toggle("mock-body-on-burger");
z.classList.toggle("mock-body-container-on-burger");
x.classList.toggle("body-on-burger");
if (s_(x)) {
document.documentElement.scrollTop = savedScroll;
}
};
Check it out:
let savedScroll;
function shrinkPage() {
let _s = (el) => document.querySelector(el),
s_ = (d) => !d.classList.contains('body-on-burger'),
x = _s('#body'),
element = _s('#mock-body'),
z = _s('#mock-body-container');
if (s_(x)) {
savedScroll = document.documentElement.scrollTop;
}
element.classList.toggle("mock-body-on-burger");
z.classList.toggle("mock-body-container-on-burger");
x.classList.toggle("body-on-burger");
if (s_(x)) {
document.documentElement.scrollTop = savedScroll;
}
};
body {
margin: 0;
background: #000;
}
.body-on-burger {
max-width: 100%;
overflow-x: hidden;
}
.mock-body-container {
height: 100vh;
}
.mock-body-container-on-burger {
height: 100vh;
transform: scale(0.4) translate(130%);
overflow: hidden;
}
.mock-body-size-change {
overflow: scroll;
}
.mock-body {
position: relative;
background: #fff;
margin-left: 50px;
}
.container {
position: fixed;
height: 50px;
width: 50px;
cursor: pointer;
}
.container #icon {
width: 16px;
height: 8px;
position: relative;
margin: 0px auto 0;
top: 40%;
}
.container #icon .bars {
height: 1px;
background: #fff;
}
.myDiv {
height: 500px;
}
.one {
background: red;
}
.two {
background: green;
}
.three {
background: blue;
}
<body id="body">
<div class="menu-activator" onclick="shrinkPage()">
<div class="container usd">
<div id="icon">
<div class="bars first"></div>
</div>
</div>
</div>
<div id="mock-body-container" class="mock-body-container">
<div id="mock-body" class="mock-body">
<div class="myDiv one"></div>
<div class="myDiv two"></div>
<div class="myDiv three"></div>
</div>
</div>
</body>
Legend: _s(el) returns first match of el and s_(d) checks if d has class body-on-burger.
The simple way to do this is to determine the change in height during the resize, and scroll that much.
const heightChange = newHeight - initialHeight;
scrollableDiv.scrollTop = scrollableDiv.scrollTop - heightChange;
In my case I am using a resize method I wrote, so I do this work inside of a window.addEventListener("mousemove", handleResize); when I know the div in actively being resized by the user.
This will still work fine with native html resizable elements, you just need to figure out how/when to listen for resize/drag events accordingly.

Categories

Resources