【问题标题】:How can I effectively pan an entire image programmatically?如何以编程方式有效地平移整个图像?
【发布时间】:2022-01-20 09:28:51
【问题描述】:

我有一个 11500x11500 的 div,它包含 400 张图像,显然溢出了视口。

我想以编程方式平移 整个 div。

我想生成一个动画,当动画结束时,整个 div 必须已经在视口中从上到下、从左到右平移。

现在,我正在将我的 11500x1500 div “拆分”为 tiles。每个图块的最大宽度和高度就是视口的宽度和高度。

我存储每个图块的坐标,然后随机选择一个,从左到右平移,然后移动到下一个。

我想知道:

  1. 我的方法是否正确,或者我的计算/方法中是否遗漏了一些东西,并且可以改进。考虑到尺寸,我很难判断我是否真的在平移整个 div
  2. 能否让平移效果感觉更“有机”/“自然”。为了确保整个 div 最终被平移,我选择每个图块并从左到右平移它,然后移动到下一个等等。这感觉有点僵硬和过于形式化。有没有办法以一个角度或一个更随机的运动进行平移,但要确保整个 div 最终会被平移?

提前感谢您的帮助。

这是jsfiddle,这是代码(为了示例/测试,每个“图像”实际上是一个包含其索引为文本的 div):

function forMs(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, time)
  })
}

let container = document.getElementById('container')

let {
  width,
  height
} = container.getBoundingClientRect()

let minLeft = window.innerWidth - width
let minTop = window.innerHeight - height

let i = 0
while (i < 400) {
  // adding "image" to the container
  let image = document.createElement('div')

  // add some text to the "image" 
  // to know what we're looking at while panning
  image.innerHTML = ''
  let j = 0
  while (j < 100) {
    image.innerHTML += ` ${i + 1}`
    j++
  }

  container.appendChild(image)

  i++
}

let coords = []
let x = 0
while (x < width) {
  let y = 0
  while (y < height) {
    coords.push({
      x,
      y
    })
    y += window.innerHeight
  }
  x += window.innerWidth
}

async function pan() {
  if (!coords.length) {
    return;
  }

  let randomIdx = Math.floor(Math.random() * coords.length)
  let [randomCoord] = coords.splice(randomIdx, 1);

  console.log(coords.length)

  container.classList.add('fast')

  // update style in new thread so new transition-duration is applied
  await forMs(10)

  // move to new yet-unpanned area
  container.style.top = Math.max(-randomCoord.y, minTop) + 'px'
  container.style.left = Math.max(-randomCoord.x, minLeft) + 'px'

  // wait (approx.) for transition to end
  await forMs(2500)

  container.classList.remove('fast')

  // update style in new thread so new transition-duration is applied
  await forMs(10)

  //pan that area
  let newLeft = -(randomCoord.x + window.innerWidth)

  if (newLeft < minLeft) {
    newLeft = minLeft
  }

  container.style.left = newLeft + 'px'

  // wait (approx.) for transition to end
  await forMs(4500)

  // move on to next random area
  await pan()
}

pan()
html,
body {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: auto;
}

* {
  margin: 0;
  padding: 0;
}

#container {
  position: absolute;
  top: 0;
  left: 0;
  text-align: left;
  width: 11500px;
  height: 11500px;
  transition: all 4s ease-in-out;
  transition-property: top left;
  font-size: 0;
}

#container.fast {
  transition-duration: 2s;
}

#container div {
  display: inline-block;
  height: 575px;
  width: 575px;
  border: 1px solid black;
  box-sizing: border-box;
  font-size: 45px;
  overflow: hidden;
  word-break: break-all;
}
&lt;div id="container"&gt;&lt;/div&gt;

【问题讨论】:

    标签: javascript html css css-transitions panning


    【解决方案1】:

    我认为可以进行以下改进:

    • 隐藏 html 和 body 上的溢出,这样用户就不能移动滚动条并干扰流程。
    • 每次都计算minLeftminTop 以考虑调整窗口大小。您可能需要ResizeObserver 来重新计算。
    • 增加转换时间以避免Cybersickness。在更坏的情况下,RNG 将首先选择右下角的瓷砖,这样你的容器将在 2 秒内移动最长!也许,您可以缩小并移动然后放大然后执行平移。或者使用任何serpentine 路径,这将使跳转更短。

    性能改进:

    • 使用变换而不是顶部,左侧用于动画。
    • 使用 will-change: transform;will-change 会让浏览器知道要优化什么。
    • 使用translate3D() 而不是translate()ref
    • 使用requestAnimationFrame。避免使用 setTimeout、setInterval。


    这是一篇旧文但很好的文章:https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/


    修改代码以使用变换:

    function forMs(time) {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve()
        }, time)
      })
    }
    
    let container = document.getElementById('container')
    let stat = document.getElementById('stats');
    let {
      width,
      height
    } = container.getBoundingClientRect()
    
    let minLeft = window.innerWidth - width
    let minTop = window.innerHeight - height
    
    let i = 0
    while (i < 400) {
      // adding "image" to the container
      let image = document.createElement('div')
    
      // add some text to the "image" 
      // to know what we're looking at while panning
      image.innerHTML = ''
      let j = 0
      while (j < 100) {
        image.innerHTML += ` ${i + 1}`
        j++
      }
    
      container.appendChild(image)
    
      i++
    }
    
    let coords = []
    let x = 0
    while (x < width) {
      let y = 0
      while (y < height) {
        coords.push({
          x,
          y
        })
        y += window.innerHeight
      }
      x += window.innerWidth
    }
    
    let count = 0;
    async function pan() {
      if (!coords.length) {
        stat.innerText = 'iteration: ' +
          (++count) + '\n tile# ' + randomIdx + ' done!!';
        stat.style.backgroundColor = 'red';
        return;
      }
      let minLeft = window.innerWidth - width
      let minTop = window.innerHeight - height
    
    
      let randomIdx = Math.floor(Math.random() * coords.length);
      randomIdx = 1; //remove after debugging
      let [randomCoord] = coords.splice(randomIdx, 1);
    
      stat.innerText = 'iteration: ' +
        (++count) + '\n tile# ' + randomIdx;
      console.log(coords.length + ' - ' + randomIdx)
    
      container.classList.add('fast')
    
      // update style in new thread so new transition-duration is applied
      await forMs(10)
    
      // move to new yet-unpanned area
      let yy = Math.max(-randomCoord.y, minTop);
      let xx = Math.max(-randomCoord.x, minLeft);
      move(xx, yy);
    
      // wait (approx.) for transition to end
      await forMs(2500)
    
      container.classList.remove('fast')
    
      // update style in new thread so new transition-duration is applied
      await forMs(10)
    
      //pan that area
      let newLeft = -(randomCoord.x + window.innerWidth)
    
      if (newLeft < minLeft) {
        newLeft = minLeft
      }
      xx = newLeft;
    
      //container.style.left = newLeft + 'px'
      move(xx, yy);
    
      // wait (approx.) for transition to end
      await forMs(4500)
    
      // move on to next random area
      await pan()
    }
    
    pan()
    
    function move(xx, yy) {
      container.style.transform = "translate3D(" + xx + "px," + yy + "px,0px)";
    }
    html,
    body {
      position: relative;
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
    
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    #container {
      text-align: left;
      width: 11500px;
      height: 11500px;
      transition: all 4s ease-in-out;
      transition-property: transform;
      font-size: 0;
      will-change: transform;
    }
    
    #container.fast {
      transition-duration: 2s;
    }
    
    #container div {
      display: inline-block;
      height: 575px;
      width: 575px;
      border: 1px solid black;
      box-sizing: border-box;
      font-size: 45px;
      overflow: hidden;
      word-break: break-all;
    }
    
    #stats {
      border: 2px solid green;
      width: 100px;
      background-color: lightgreen;
      position: fixed;
      opacity: 1;
      top: 0;
      left: 0;
      z-index: 10;
    }
    <div id=stats>iteration: 1 tile# 11</div>
    <div id="container"></div>


    请注意,我还没有实现上面 sn-p 中的所有内容。

    【讨论】:

    • 非常感谢您抽出宝贵时间提供如此详尽的答案。特别是变换建议真的很有帮助,因为改变顶部/左侧确实很颠簸。我会更新并回复你。谢谢!
    • 不幸的是,使用 CSS 转换的代价是在转换发生时很多图像没有被渲染。并且它们仅在过渡结束时呈现。而在我最初的实现中,过渡更加颠簸,但所有图像都会不断呈现。
    • 事情发展得太快了。 :( 有先进的技术可以实现higher fps。您需要尝试使用实际图像而不是仅文本,最后这很重要。如果您加载那么多图像,您还需要检查内存要求。对于加载时间,您可以需要使用单个精灵。
    猜你喜欢
    • 1970-01-01
    • 2017-04-04
    • 1970-01-01
    • 2013-06-10
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 2023-03-03
    • 2017-01-28
    相关资源
    最近更新 更多