Home > Net >  Zoom an area of a page to fullscreen on click with HTML / CSS / JavaScript
Zoom an area of a page to fullscreen on click with HTML / CSS / JavaScript

Time:07-06

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.

  • Related