2

The animation is similar to one slide transition in PowerPoint. I would do it using skew and translate (from random position on the margin of the page) transforms. I started using CSS sprites and CSS grid. The first problem I encounter is a spacing in the grid between pieces, spacing that is not explained in Chrome DevTools. I'm OK with using anime.js or other libraries.

The problem below is the spacing between pieces and that they do not show the full portions of the image that are needed. The original image is here.

/* The photo resolution: 2400 x 1600 */
.slideshow {
    display: grid;
    grid-template-rows: auto auto auto;
    grid-template-columns: auto auto auto;
}

.tile {
    background-image: url(https://unsplash.com/photos/hzgs56Ze49s/download?force=true&w=2400);
    /* width: 800px;
    height: 533px; */
    width: calc(800px / 2);
    height: calc(533px / 2);
    transform: scale(0.5, 0.5);
}
 
.piece1 {
    background-position: 0 0;
    grid-row: 1/1;
    grid-column: 1/1;
}
.piece2 {
    background-position: -800px 0;
    grid-row: 1/1;
    grid-column: 2/2;
}
.piece3 {
    background-position: -1600px 0;
    grid-row: 1/1;
    grid-column: 3/3;
}

.piece4 {
    background-position: 0 -533px;
    grid-row: 2/2;
    grid-column: 1/1;
}
.piece5 {
    background-position: -800px -533px;
    grid-row: 2/2;
    grid-column: 2/2;
}
.piece6 {
    background-position: -1600px -533px;
    grid-row: 2/2;
    grid-column: 3/3;
}

.piece7 {
    background-position: 0 -1066px;
    grid-row: 3/3;
    grid-column: 1/1;
}
.piece8 {
    background-position: -800px -1066px;
    grid-row: 3/3;
    grid-column: 2/2;
}
.piece9 {
    background-position: -1600px -1066px;
    grid-row: 3/3;
    grid-column: 3/3;
}
<div class="slideshow">
  <div class="tile piece1"></div>
  <div class="tile piece2"></div>
  <div class="tile piece3"></div>

  <div class="tile piece4"></div>
  <div class="tile piece5"></div>
  <div class="tile piece6"></div>

  <div class="tile piece7"></div>
  <div class="tile piece8"></div>
  <div class="tile piece9"></div>
</div>

I am not sure yet if the way I started walking upon is going to work.

Thank you.

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
silviubogan
  • 3,343
  • 3
  • 31
  • 57

2 Answers2

2

One way to do it with a few changes:

1) background-position, should be "left top", "top", "right top", "left", "center", "right", "bottom left", "bottom center", and "bottom right" to slice the images into 9 pieces.
https://www.w3schools.com/cssref/pr_background-position.asp

2) Since original image's dimensions are 2400 x 1600, set the "background-size" to be smaller, then calculate using calc according to smaller dimensions, say:

width: calc(600px / 3);
height: calc(600px / 3);

3) Set tiles position to be absolute.

4) Designate each tile's absolute positions using top and left.

5) Designate each tile with unique IDs.

6) Use javascript's setInterval and if else statement to change the elements' absolute positions, using each element's id to decide how their top and left attributes should change overtime.

7) In the example I use the button "Click Me" to execute the script, but you can do it with onload or however you want to call the moveElements() function.

<style>
    /* The photo resolution: 2400 x 1600 */
.slideshow {
    display: grid;
    grid-template-rows: auto auto auto;
    grid-template-columns: auto auto auto;
}

.tile {
    background-image: url(https://unsplash.com/photos/hzgs56Ze49s/download?force=true&w=2400);
    background-size: 600px 600px;
    /* width: 800px;
    height: 533px; */
    width: calc(600px / 3);
    height: calc(600px / 3);
    transform: scale(0.5, 0.5);
    position:absolute;
}

.piece1 {
    background-position: left top;
    grid-row: 1/1;
    grid-column: 1/1;
    top:0;
    left:0;
}
.piece2 {
    background-position: top;
    grid-row: 1/1;
    grid-column: 2/2;
    top:0;
    left:250;
}
.piece3 {
    background-position: right top;
    grid-row: 1/1;
    grid-column: 3/3;
    top:0;
    left:500;
}

.piece4 {
    background-position: left;
    grid-row: 2/2;
    grid-column: 1/1;
    top:250;
    left:0;
}
.piece5 {
    background-position: center;
    grid-row: 2/2;
    grid-column: 2/2;
    top:250;
    left: 250;
}
.piece6 {
    background-position: right;
    grid-row: 2/2;
    grid-column: 3/3;
    top:250;
    left:500;
}

.piece7 {
    background-position: bottom left;
    grid-row: 3/3;
    grid-column: 1/1;
    top:500;
    left:0;
}
.piece8 {
    background-position: bottom center;
    grid-row: 3/3;
    grid-column: 2/2;
    top:500;
    left:250;
}
.piece9 {
    background-position: bottom right;
    grid-row: 3/3;
    grid-column: 3/3;
    top:500;
    left:500;
}
</style>
<p><button onclick="moveElements()">Click Me</button></p> 
<div class="slideshow">
  <div id="topleft" class="tile piece1"></div>
  <div id="top" class="tile piece2"></div>
  <div id="topright" class="tile piece3"></div>

  <div id="left" class="tile piece4"></div>
  <div id="center" class="tile piece5"></div>
  <div id="right" class="tile piece6"></div>

  <div id="bottomleft" class="tile piece7"></div>
  <div id="bottomcenter" class="tile piece8"></div>
  <div id="bottomright" class="tile piece9"></div>
</div>


<script>
function moveElements() {
  var elem1 = document.getElementById("topleft");   
  var pos1 = 0;
  var id1 = setInterval(frame1, 5);
  function frame1() {
    if (pos1 == 151) {
      clearInterval(id1);
    } else {
      pos1++; 
      elem1.style.top = pos1 + "px"; 
      elem1.style.left = pos1 + "px"; 
    }
  }

  var elem2 = document.getElementById("top");   
  var pos2 = 0;
  var id2 = setInterval(frame2, 5);
  function frame2() {
    if (pos2 == 151) {
      clearInterval(id2);
    } else {
      pos2++; 
      elem2.style.top = pos2 + "px"; 

    }
  }

  var elem3 = document.getElementById("topright");   
  var pos3t = 0;
  var pos3l = 500;
  var id3 = setInterval(frame3, 5);
  function frame3() {
    if (pos3t == 151) {
      clearInterval(id3);
    } else {
      pos3t++; 
      pos3l--;
      elem3.style.top = pos3t + "px"; 
      elem3.style.left = pos3l + "px"; 
    }
  }

  var elem4 = document.getElementById("left");   
  var pos4 = 0;
  var id4 = setInterval(frame4, 5);
  function frame4() {
    if (pos4 == 151) {
      clearInterval(id4);
    } else {
      pos4++; 
      elem4.style.left = pos4 + "px"; 
    }
  }

  // element5, the center tile stays still

  var elem6 = document.getElementById("right");   
  var pos6 = 500;
  var id6 = setInterval(frame6, 5);
  function frame6() {
    if (pos6 == 349) {
      clearInterval(id6);
    } else {
      pos6--; 
      elem6.style.left = pos6 + "px"; 
    }
  }

  var elem7 = document.getElementById("bottomleft");   
  var pos7t = 500;
  var pos7l = 0;
  var id7 = setInterval(frame7, 5);
  function frame7() {
    if (pos7t == 349) {
      clearInterval(id7);
    } else {
      pos7t--;
      pos7l++; 
      elem7.style.top = pos7t + "px"; 
      elem7.style.left = pos7l + "px"; 
    }
  }

  var elem8 = document.getElementById("bottomcenter");   
  var pos8 = 500;
  var id8 = setInterval(frame8, 5);
  function frame8() {
    if (pos8 == 349) {
      clearInterval(id8);
    } else {
      pos8--; 
      elem8.style.top = pos8 + "px"; 
    }
  }

  var elem9 = document.getElementById("bottomright");   
  var pos9 = 500;
  var id9 = setInterval(frame9, 5);
  function frame9() {
    if (pos9 == 349) {
      clearInterval(id9);
    } else {
      pos9--; 
      elem9.style.top = pos9 + "px"; 
      elem9.style.left = pos9 + "px"; 
    }
  }
}
</script>
hiew1
  • 1,394
  • 2
  • 15
  • 23
1

I would consider all the image layer above each other using position:absolute and I will rely on mask (or clip-path) to show only a portion of it. Then you can easily apply a translation.

Hover to see the effect:

.slideshow {
  width: 400px;
  height: 261px;
  margin:100px auto;
  position:relative;
  background-image: url(https://unsplash.com/photos/hzgs56Ze49s/download?force=true&w=2400);
  background-size: 0 0;
}

.tile {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: inherit;
  background-size: cover;
  -webkit-mask:linear-gradient(#fff,#fff) no-repeat;
  -webkit-mask-size:33.4% 33.4%;
  -webkit-mask-position:var(--p);
          mask:linear-gradient(#fff,#fff) no-repeat;
          mask-size:33.4% 33.4%;
          mask-position:var(--p);
   transition:0.5s;
}

.piece1 {--p:top    left;  --t:-10%, -10%;}
.piece2 {--p:center left;  --t:-10%,  0%;}
.piece3 {--p:bottom left;  --t:-10%,  10%;}

.piece4 {--p:top    center;--t: 0%,  -10%;}
.piece5 {--p:center center;--t: 0%,   0%;}
.piece6 {--p:bottom center;--t: 0%,   10%;}

.piece7 {--p:top    right; --t: 10%, -10%;}
.piece8 {--p:center right; --t: 10%,  0%;}
.piece9 {--p:bottom right; --t: 10%,  10%;}

.slideshow:hover .tile{
   transform:translate(var(--t));
}

body {
 background:#f2f2f2;
}
<div class="slideshow">
  <div class="tile piece1"></div>
  <div class="tile piece2"></div>
  <div class="tile piece3"></div>

  <div class="tile piece4"></div>
  <div class="tile piece5"></div>
  <div class="tile piece6"></div>

  <div class="tile piece7"></div>
  <div class="tile piece8"></div>
  <div class="tile piece9"></div>
</div>

You can optimize with a scale effect where you don't need a lot of code and you can reuse the mask-position with transform-origin

.slideshow {
  width: 400px;
  height: 261px;
  margin:50px auto;
  position:relative;
  background-image: url(https://unsplash.com/photos/hzgs56Ze49s/download?force=true&w=2400);
  background-size: 0 0;
  transition:0.5s;
}

.tile {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: inherit;
  background-size: cover;
  -webkit-mask:linear-gradient(#fff,#fff) no-repeat;
  -webkit-mask-size:33.4% 33.4%;
  -webkit-mask-position:var(--p);
          mask:linear-gradient(#fff,#fff) no-repeat;
          mask-size:33.4% 33.4%;
          mask-position:var(--p);
   transition:inherit;
   transform-origin:var(--p);
}

.piece1 {--p:top    left;}
.piece2 {--p:center left;}
.piece3 {--p:bottom left;}

.piece4 {--p:top    center;}
.piece5 {--p:center center;}
.piece6 {--p:bottom center;}

.piece7 {--p:top    right;}
.piece8 {--p:center right;}
.piece9 {--p:bottom right;}

.slideshow:hover .tile{
   transform:scale(0.8);
}

.slideshow:hover {
   transform:scale(1.25);
}

body {
 background:#f2f2f2;
}
<div class="slideshow">
  <div class="tile piece1"></div>
  <div class="tile piece2"></div>
  <div class="tile piece3"></div>

  <div class="tile piece4"></div>
  <div class="tile piece5"></div>
  <div class="tile piece6"></div>

  <div class="tile piece7"></div>
  <div class="tile piece8"></div>
  <div class="tile piece9"></div>
</div>

You can easily scale to a 4x4 or any NxN grid:

.slideshow {
  --n:4;
  
  width: 400px;
  height: 261px;
  margin:100px auto;
  position:relative;
  background-image: url(https://unsplash.com/photos/hzgs56Ze49s/download?force=true&w=2400);
  background-size: 0 0;
   transition:0.5s;
}

.tile {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: inherit;
  background-size: cover;
  -webkit-mask:linear-gradient(#fff,#fff) no-repeat;
  -webkit-mask-size:calc(100%/var(--n) + 1px) calc(100%/var(--n) + 1px);
  -webkit-mask-position:var(--p);
          mask:linear-gradient(#fff,#fff) no-repeat;
          mask-size:calc(100%/var(--n) + 1px) calc(100%/var(--n) + 1px);
          mask-position:var(--p);
   transition:inherit;
   transform-origin:var(--p);
}

.tile:nth-child(1)  {--p:calc(0*100%/(var(--n) - 1)) calc(0*100%/(var(--n) - 1));}
.tile:nth-child(2)  {--p:calc(1*100%/(var(--n) - 1)) calc(0*100%/(var(--n) - 1));}
.tile:nth-child(3)  {--p:calc(2*100%/(var(--n) - 1)) calc(0*100%/(var(--n) - 1));}
.tile:nth-child(4)  {--p:calc(3*100%/(var(--n) - 1)) calc(0*100%/(var(--n) - 1));}

.tile:nth-child(5)  {--p:calc(0*100%/(var(--n) - 1)) calc(1*100%/(var(--n) - 1));}
.tile:nth-child(6)  {--p:calc(1*100%/(var(--n) - 1)) calc(1*100%/(var(--n) - 1));}
.tile:nth-child(7)  {--p:calc(2*100%/(var(--n) - 1)) calc(1*100%/(var(--n) - 1));}
.tile:nth-child(8)  {--p:calc(3*100%/(var(--n) - 1)) calc(1*100%/(var(--n) - 1));}

.tile:nth-child(9)  {--p:calc(0*100%/(var(--n) - 1)) calc(2*100%/(var(--n) - 1));}
.tile:nth-child(10) {--p:calc(1*100%/(var(--n) - 1)) calc(2*100%/(var(--n) - 1));}
.tile:nth-child(11) {--p:calc(2*100%/(var(--n) - 1)) calc(2*100%/(var(--n) - 1));}
.tile:nth-child(12) {--p:calc(3*100%/(var(--n) - 1)) calc(2*100%/(var(--n) - 1));}

.tile:nth-child(13) {--p:calc(0*100%/(var(--n) - 1)) calc(3*100%/(var(--n) - 1));}
.tile:nth-child(14) {--p:calc(1*100%/(var(--n) - 1)) calc(3*100%/(var(--n) - 1));}
.tile:nth-child(15) {--p:calc(2*100%/(var(--n) - 1)) calc(3*100%/(var(--n) - 1));}
.tile:nth-child(16) {--p:calc(3*100%/(var(--n) - 1)) calc(3*100%/(var(--n) - 1));}


.slideshow:hover > *{
   transform:scale(0.8);
}
.slideshow:hover {
   transform:scale(1.25);
}

body {
 background:#f2f2f2;
}
<div class="slideshow">
  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>

  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>

  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>
  
  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>
  <div class="tile"></div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • It is wonderful! But I have a small problem: there is a white line between the second and third columns. I think it is related to `calc(100% / 3)`. Do you know how can this be corrected? – silviubogan Mar 30 '20 at 09:45
  • @silviubogan yes rounding issue, you can increase the mask-size. Check the update – Temani Afif Mar 30 '20 at 09:50
  • If it is possible, can you tell me how can this be changed to make a 4x4 grid (in the question 3x3 was just an example)? Thank you. – silviubogan Mar 30 '20 at 09:54
  • @silviubogan check the update, added an example with 4x4 – Temani Afif Mar 30 '20 at 10:07