Код: Выделить всё
#problemsКод: Выделить всё
#solutions[*]Scroll-up: circle should shrink max → 0, smoothly revealing Problem 5 again (no jump).
Код: Выделить всё
(() => {
// Get the two sections: the problems you scroll past and the solutions you reveal
const problems = document.getElementById('problems');
const solutions = document.getElementById('solutions');
const body = document.body;
const html = document.documentElement;
/* -------- Robust way to read the current vertical scroll position -------- */
const getScrollY = () =>
window.pageYOffset ||
html.scrollTop ||
body.scrollTop ||
window.visualViewport?.offsetTop ||
0;
/* -------- Calculate the vertical boundaries where the clip animation starts/ends -------- */
let viewportHeight = window.innerHeight;
// The top position of the solutions section
let topOfSolutions = problems.offsetTop + problems.offsetHeight;
// When to start revealing solutions: one viewportHeight before the section
let startReveal = topOfSolutions - viewportHeight;
// When to be fully revealed
let endReveal = topOfSolutions;
let revealRatio = 0; // Will go from 0 (hidden) to 1 (fully revealed)
let isLocked = false; // Are we locking the page scroll to control the reveal?
/* Maximum radius for the circular clip: half the diagonal of the viewport */
const maxRadius = () => Math.hypot(window.innerWidth / 2, window.innerHeight / 2);
/* Apply the circular clip with given radius (in pixels) */
const setRadius = (r) => {
const clip = `circle(${r}px at 50% 50%)`;
solutions.style.clipPath = clip;
solutions.style.webkitClipPath = clip;
};
/* -------- Functions to lock/unlock the normal page scroll -------- */
const lockScroll = (yPos) => {
isLocked = true;
body.style.overflow = 'hidden';
window.scrollTo(0, yPos);
};
const unlockScroll = (yPos) => {
isLocked = false;
body.style.overflow = 'auto';
window.scrollTo({
top: yPos,
behavior: 'auto'
});
};
/* ---------- Main scroll handler: controls the clip during scrolling ---------- */
window.addEventListener('scroll', () => {
if (isLocked) return; // if we're locked, ignore normal scroll events
const currentY = getScrollY();
if (currentY < startReveal) {
// Above the start zone: keep circle closed
revealRatio = 0;
setRadius(0);
return;
}
if (currentY > endReveal) {
// Below the end zone: circle fully open
revealRatio = 1;
setRadius(maxRadius());
return;
}
// Inside the transition zone: compute how far we are in it
revealRatio = (currentY - startReveal) / (endReveal - startReveal);
setRadius(revealRatio * maxRadius());
// Lock the scroll so we can use wheel/touch to drive the reveal
// Decide which edge to snap to if the user reverses direction
const midpoint = (startReveal + endReveal) / 2;
lockScroll(currentY < midpoint ? startReveal : endReveal);
}, {
passive: true
});
/* ---------- Helper to advance the reveal by a delta, then release lock if done ---------- */
const advanceReveal = (deltaY) => {
// convert delta scroll into a change in ratio
revealRatio = Math.max(0, Math.min(1, revealRatio + deltaY / viewportHeight));
setRadius(revealRatio * maxRadius());
if (revealRatio === 1) unlockScroll(endReveal); // fully revealed → resume normal scroll down
if (revealRatio === 0) unlockScroll(startReveal); // fully hidden → resume normal scroll up
};
/* ---------- Mouse wheel while locked: drive the reveal ---------- */
window.addEventListener('wheel', (e) => {
if (!isLocked) return; // only intercept if we're in the locked state
e.preventDefault(); // prevent the page from scrolling
advanceReveal(e.deltaY);
}, {
passive: false
});
/* ---------- Touch drag while locked: similar to wheel ---------- */
window.addEventListener('touchmove', (e) => {
if (!isLocked) return;
e.preventDefault();
const touch = e.touches[0];
if (touch._prevY === undefined) {
touch._prevY = touch.clientY;
}
const dy = touch._prevY - touch.clientY;
touch._prevY = touch.clientY;
advanceReveal(dy);
}, {
passive: false
});
/* ---------- Recalculate dimensions on resize ---------- */
window.addEventListener('resize', () => {
viewportHeight = window.innerHeight;
topOfSolutions = problems.offsetTop + problems.offsetHeight;
startReveal = topOfSolutions - viewportHeight;
endReveal = topOfSolutions;
if (!isLocked) {
// update current clip if not locked
setRadius(revealRatio * maxRadius());
}
});
})();< /code>
html,
body {
height: 100%;
margin: 0;
font-family: sans-serif
}
/* Problems */
#problems {
scroll-snap-type: y mandatory;
position: relative
}
#problems h1 {
position: sticky;
top: 0;
background: #fff;
padding: 1rem;
text-align: center;
z-index: 1
}
.card {
height: 100vh;
scroll-snap-align: start;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: #fff
}
.card:nth-child(2) {
background: #90caf9
}
.card:nth-child(3) {
background: #a5d6a7
}
.card:nth-child(4) {
background: #ce93d8
}
.card:nth-child(5) {
background: #ffcc80
}
/* Solutions (masked) */
#solutions {
position: fixed;
inset: 0;
background: #000;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
clip-path: circle(0px at 50% 50%);
-webkit-clip-path: circle(0px at 50% 50%);
pointer-events: none;
z-index: 50;
}< /code>
Do you know these problems?
Problem 1
Problem 2
Problem 3
Problem 4
Problem 5
Solution
Подробнее здесь: https://stackoverflow.com/questions/796 ... -shrink-on
Мобильная версия