I am having trouble finding what I'm looking for with google searches. I want to create a full-page image (that is, takes up the entire viewport). It might be one big image, a series of smaller images that fit together to look like one big one, or even an image built entirely in CSS. I'm not sure the best way to go about it yet.
When you click on a button on a part of the image, it smoothly zooms into that area of the page until that area takes up the entire viewport, pushing the other content away temporarily. Something similar to the effect that is created in Prezi presentations like this one: https://www.youtube.com/watch?v=h2_6bfVc9lo
To make things simple, let's say I have a flexbox with 2 items. Clicking into one item would make that item expand / zoom in until it takes up the entire viewport, pushing the other item off the screen. (I have commented out the placeholder JS code to prevent the Run Code from breaking.)
/* const box1text = document.getElementById('item1');
function zoombox1(event){
// Code that smoothly zooms into Box 1 until it takes up the entire viewport (Box 2 is pushed out of the viewport to the right)
}
box1text.addEventListener('click', zoombox1);
const box2text = document.getElementById('item2');
function zoombox2(event){
// Code that smoothly zooms into Box 2 until it takes up the entire viewport (Box 1 is pushed out of the viewport to the left)
}
box2text.addEventListener('click', zoombox2); */
.container {
display: flex;
flex-direction: row;
}
.item {
border: 1px solid black;
padding: 25px;
width: 50%;
text-align: center;
}
<div >
<div >Box 1 (clicking this text will expand Box 1 to full size of viewport, pushing Box 2 off the edge of the screen)</div>
<div >Box 2 (clicking this text will expand Box 2 to full size of viewport, pushing Box 1 off the edge of the screen)</div>
</div>
I am a bit lost about what I can do in JS, or even if there's a way in CSS only to achieve this effect. Google kept pointing to accordion-style expandable / collapsible content, which isn't what I'm looking for. Any help is appreciated!
CodePudding user response:
You can try to use TweenMax
to achieve it.
const root = document.documentElement;
const body = document.body;
const pages = document.querySelectorAll(".page");
const tiles = document.querySelectorAll(".tile");
//Bind listener
for (let i = 0; i < tiles.length; i ) {
const tile = tiles[i];
const page = pages[i];
tile.addEventListener("click", function() {
animateArea(tile, page);
});
page.addEventListener("click", function() {
animateArea(page, tile);
});
}
// Animate with TweenLite
const animateArea = (fromHero, toHero) => {
const clone = fromHero.cloneNode(true);
const from = calculatePosition(fromHero);
const to = calculatePosition(toHero);
TweenLite.set([fromHero, toHero], {
visibility: "hidden"
});
TweenLite.set(clone, {
position: "absolute",
margin: 0
});
body.appendChild(clone);
const style = {
x: to.left - from.left,
y: to.top - from.top,
width: to.width,
height: to.height,
autoRound: false,
ease: Power1.easeOut,
onComplete: () => {
TweenLite.set(toHero, {
visibility: "visible"
});
body.removeChild(clone);
}
};
TweenLite.set(clone, from);
TweenLite.to(clone, 0.3, style)
}
// Calculate the position
const calculatePosition = (element) => {
const rect = element.getBoundingClientRect();
const scrollTop = window.pageYOffset || root.scrollTop || body.scrollTop || 0;
const scrollLeft = window.pageXOffset || root.scrollLeft || body.scrollLeft || 0;
const clientTop = root.clientTop || body.clientTop || 0;
const clientLeft = root.clientLeft || body.clientLeft || 0;
return {
top: Math.round(rect.top scrollTop - clientTop),
left: Math.round(rect.left scrollLeft - clientLeft),
height: rect.height,
width: rect.width,
};
}
html,
body {
height: 100%;
}
.green-box {
height: 154px;
background: #70c26f;
}
.tile-container {
position: absolute;
top: 27px;
right: 0;
left: 0;
text-align: center;
user-select: none;
}
.tile {
width: 100px;
height: 100px;
margin: 4px;
cursor: pointer;
display: inline-block;
}
.page-container {
visibility: hidden;
}
.page {
cursor: pointer;
position: absolute;
height: 100vh;
width: 100vw;
top: 0;
left: 0;
position: fixed;
}
.area-1 {
background: #F4DB33;
}
.area-2 {
background: #972FF8;
}
<div ></div>
<div >
<div ></div>
<div ></div>
</div>
<div >
<div ></div>
<div ></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.3/TweenMax.min.js"></script>
CodePudding user response:
Plain Javascript and CSS solution
Essentially, a click listener is added to a designated set of elements. On click, that element is cloned and a new one is sneakily placed on top of the old one in the same place, but taken out of the document flow with position: absolute
.
The cloned element is given a css class that has an animation property that will make it expand to the full screen. Then that cloned element is assigned a click listener so it can reverse the animation and remove itself from the document once the animation has ended.
There is a bit of trickery in the shrink
function. The browser won't re-trigger animations just because you add another class that has one.
So, setting the animation to 'none'
in javascript takes precedence over the CSS animation. Then accessing the offsetHeight
property forces a reflow of the browser, and allows animations to play again. Then, removing the javascript animation property with ''
allows the CSS to take control of the animation again.
const boxes = document.querySelectorAll(".box")
const container = document.querySelector(".container")
const shrink = (e) => {
const el = e.target
// Remove cloned element from DOM after animation is over
el.addEventListener("animationend", (e) => e.target.remove())
// Trigger browser reflow to start animation
el.style.animation = 'none';
el.offsetHeight
el.style.animation = ''
el.classList.add("shrink-down")
}
const toggleFullScreen = (e) => {
// Get position values for element
const {
top,
left
} = e.target.getBoundingClientRect()
// Clone the element and its children
let fullScreen = e.target.cloneNode(true)
// Set top and left with custom property
fullScreen.style.setProperty("--inset", `${top}px auto auto ${left}px`)
// Add class with animation and position
fullScreen.classList.add("full-screen")
// Listen for click to close full screen
fullScreen.addEventListener("click", shrink)
// Place in container over element to expand
container.appendChild(fullScreen)
}
// Add click listeners on all boxes
boxes.forEach(box => {
box.addEventListener("click", toggleFullScreen)
})
/* Layout Styles */
body {
margin: 0;
}
.container {
min-height: 100vh;
display: flex;
background-color: blue;
justify-content: center;
align-items: center;
gap: 1rem;
}
.box {
width: 100px;
height: 100px;
}
.box1 {
background-color: yellow;
}
.box2 {
background-color: green;
}
/* BEGIN ANIMATION STYLES */
.full-screen {
position: fixed;
animation: go-full-screen forwards 500ms ease-in-out;
inset: var(--inset);
}
.shrink-down {
animation: go-full-screen reverse backwards 500ms ease-in-out !important;
}
@keyframes go-full-screen {
from {
inset: var(--inset);
}
to {
height: 100%;
width: 100%;
inset: 0;
}
}
<div >
<div ></div>
<div ></div>
</div>
This is an isolated example, so your mileage may vary! Depends on a lot of style choices you will have made by the time you implement this solution.