如何以编程方式有效地平移整个图像?

Posted

技术标签:

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

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

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

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

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

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

我想知道:

    我的方法是否正确,或者我的计算/方法中是否遗漏了一些东西,并且可以改进。考虑到尺寸,我很难判断我是否真的在平移整个 div 能否让平移效果感觉更“有机”/“自然”。为了确保整个 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;

【问题讨论】:

【参考方案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。您需要尝试使用实际图像而不是仅文本,最后这很重要。如果您加载那么多图像,您还需要检查内存要求。对于加载时间,您可以需要使用单个精灵。

以上是关于如何以编程方式有效地平移整个图像?的主要内容,如果未能解决你的问题,请参考以下文章

我如何在iOS上以编程方式为图像着色?

python图像处理(图像平移)

python图像处理(图像平移)

matlab 图像平移

如何使用平移手势

以编程方式更改浮动按钮图像大小