Skip to content

Instantly share code, notes, and snippets.

@latobibor
Created July 4, 2024 22:20
Show Gist options
  • Save latobibor/941627df030d1651b703ebafbc02b645 to your computer and use it in GitHub Desktop.
Save latobibor/941627df030d1651b703ebafbc02b645 to your computer and use it in GitHub Desktop.
Scrolling in one div moves the other one
/* This is very primitive styling, I kept it like this as style is now not important.
If you change the size make sure the scrollbar is visible as without it nothing will happen.
*/
.controller-div {
background-color: #ddffee;
width: 100px;
height: 200px;
max-height: 100px;
overflow-y: scroll;
border-radius: 15px;
}
import styles from './controller-div.module.css';
interface ControllerDivProps {
// source of UIEvent hack: https://freshman.tech/snippets/typescript/fix-value-not-exist-eventtarget/
onScroll: (event: React.UIEvent<HTMLDivElement, UIEvent> & { target: HTMLElement }) => void;
}
export function ControllerDiv({ onScroll }: ControllerDivProps) {
return (
<div className={styles['controller-div']} onScroll={onScroll}>
Scroll me! Scroll me! Scroll me! Scroll me! Scroll me! Scroll me! Scroll me! Scroll me!
</div>
);
}
import { memo } from 'react';
import { ControllerDiv } from './controller-div';
import { MovableBlock } from './movable-block';
import { useVerticalScrollPercentage } from './use-vertical-scroll-percentage';
export function MainComponent() {
const { onScroll, verticalPercentage } = useVerticalScrollPercentage();
return (
<main>
<ControllerDiv onScroll={onScroll} />
<MovableBlock yPosition={verticalPercentage} />
<OtherThingsMemo />
</main>
);
}
function OtherThings() {
console.log('Other things got rendered!');
return (
<>
<input name="input-1" />
<input name="input-2" />
</>
);
}
const OtherThingsMemo = memo(OtherThings);
.movable-block {
position: fixed;
/* I just stick it on the right, so it won't overlap with the controller div on the left */
right: 0;
background-color: darkgoldenrod;
}
import styles from './movable-block.module.css';
const HEIGHT = 50;
const HUNDRED_PERCENT = 100;
interface MovableBlockProps {
yPosition: number;
}
export function MovableBlock({ yPosition }: MovableBlockProps) {
const LOWER_BOUND = (yPosition * HEIGHT) / HUNDRED_PERCENT;
// There is a very neat trick you can use in CSS: 100vh equals the entire screen height (vh = view height).
// Therefore you can directly use percentage value to position on the screen.
// We also need to factor in the size of the block; if we do not then at 100vh it scrolls out of the screen.
// CSS calc can convert between percentage based values and pixel based values, how cool!
const top = `calc(${yPosition}vh - ${LOWER_BOUND}px)`;
return (
<div
className={styles['movable-block']}
style={{
top,
width: `${HEIGHT}px`,
height: `${HEIGHT}px`,
}}>
Hi!
</div>
);
}
import { useState } from 'react';
export function useVerticalScrollPercentage() {
const [verticalPercentage, setVerticalPercentage] = useState(0);
function onScroll(event: React.UIEvent<HTMLElement> & { target: HTMLElement }) {
const lowestPointForScrollTop = event.target.scrollHeight - event.target.clientHeight;
// For sake of mental ease the values returned should fall between 0 and 100 as they represent percentage
const percentageOfHeight = Math.floor((event.target.scrollTop / lowestPointForScrollTop) * 100);
setVerticalPercentage(percentageOfHeight);
}
return {
onScroll,
verticalPercentage,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment