【问题标题】:2D Infinitely looping Array of elements二维无限循环元素数组
【发布时间】:2017-09-02 10:43:59
【问题描述】:

目标:

这个想法是创建一个元素网格(例如图片库),它可以在两个轴上无限循环地滚动。 不应该有漏洞,也不应该有太多的随机性(避免让相同的元素随机脱离自身)。这不管首先有多少元素(通过 16 个(4*4)个元素的网格无限循环似乎很容易,而不是超过 17 个(17*1)。(我的猜测是任何素数根据定义,元素的数量很难制作网格)。

所以我实际上找到了一个很棒的工作示例: http://www.benstockley.com/

它实际上比我想象的更接近(可能更好)。现在它正在使用画布,我尝试查看 javascript,它是一个 30000 行缩小的脚本,所以我真的无法阅读它背后的任何核心逻辑。



数学方面/解决问题:

这是问题背后的逻辑和理论,涉及的数学和思维方式。 程序应如何处理元素列表,以便我们没有孔、无限网格、所有轴上的元素的最佳重新分配。

我的猜测是它必须是程序性的。我不确定我们是否应该在每个轴上创建网格或循环遍历列表(有点像数独?我不知道);



实用方面/UI/UX:

关于所涉及的技术、代码片段的任何建议。我猜它经典的 DOM 已经不碍事了,并且以某种方式画布或 2D webgl 将是强制性的。但我很想听听这方面的任何建议。

除了所有元素的网格处理。在 DOM 或渲染器中探索 2D 无限或巨大布局所涉及的 UI 和 UX 在某种程度上并不经典。欢迎提供最好的技术或建议。



示例:

我欢迎任何与此问题有些相似的工作示例。

【问题讨论】:

  • 你想象元素列表变得非常大吗?
  • 可能是的。在我的直接用例中,它不会超过 40 个元素左右。但是考虑一种方法来处理一个庞大的元素列表(可能有一百个或更多)可能会非常有趣,因为它可以有很多应用程序。当我们开始平移和缩放时,也许我们甚至不知道孔列表(客户端)。因此,内容的微小和潜在的无限(程序?)组织对我来说都会很有趣。
  • 基于 benstockley.com,我想我设法确定了一个公式,可以让我找到将哪个元素放入哪个坐标中。看看我是否向下走一排 (Y+1) 并向左走六次 (X-6) 我总是找到相同的图像。所以我猜这个公式是:AbsoluteIndex = Xn - (6Yn) 然后AbsoluteIndex %(modulo) SumOfElements = ElementIndex 虽然这很有趣,但我确实认为这并不理想,就像我们有 300 个元素一样,Y 线之间的索引偏移应该高于 6。我们是否应该确定这一点基于总数或元素,或可见元素的总数?

标签: javascript math canvas logic


【解决方案1】:

我有一个 fiddle 来设置你的二维网格。

它通过使用水平和垂直“步长”来发挥作用。因此,在网格中向右移动一步会提高列表中的水平步长。向下移动一步会增加列表中的垂直步长(并且它们会累积)。

我们允许列表中的前进在到达末尾时循环回零。

使用 1 的水平步长可能是有意义的(因此您的网格中的一行将保持您的列表顺序)。对于垂直步长,您需要一个与列表长度不共享公约数的整数。虽然不能保证,但我使用列表长度的(四舍五入)平方根作为在很多情况下都可以使用的东西。

我将在这里重现小提琴:

var list = ['red','green','blue','cyan','orange','yellow','pink'];

var hstep = 1;
var vstep = Math.ceil(Math.sqrt(list.length));

function getListItem(x,y) {
	var index = x * hstep + y * vstep;
  return list[index % list.length];
}

var elementSize = 30;
var gutterSize = 10;

function getOffset(x,y) {
  return [10 + (elementSize + gutterSize) * x, 10 + (elementSize + gutterSize) * y];
}

var frame = $('.frame');

function drawElement(x,y) {
	var listItem = getListItem(x,y);
  var offsets = getOffset(x,y);
  var element = $('<div></div>').addClass('element').css({
  	left: offsets[0] + 'px',
    top: offsets[1] + 'px',
    'background-color': listItem
  });
  frame.append(element);
}

function drawElements() {
  var x = 0, y = 0;
  while (10 + (elementSize + gutterSize) * x < frame.width()) {
    while (10 + (elementSize + gutterSize) * y < frame.height()) {
    	drawElement(x,y);
      y++;
    }
    y = 0;
    x++;
  }
}

drawElements();
.frame {
  border: 2px solid black;
  margin: 40px auto;
  height: 300px;
  width: 300px;
  position: relative;
  overflow: hidden;
}

.frame .element {
  position: absolute;
  width: 30px;
  height: 30px;
}

.buttons {
  position: absolute;
  top: 0px;
  width: 100%;
}

.buttons button {
  position: absolute;
  width: 30px;
  height: 30px;
  padding: 5px;
}

button.up {top: 0px; left: 46%;}
button.down {top: 355px; left: 46%;}
button.left {top: 160px; left: 15px;}
button.right {top: 160px; right: 15px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="frame">

</div>
<div class="buttons">
  <button class="up">&uarr;</button>
  <button class="down">&darr;</button>
  <button class="left">&larr;</button>
  <button class="right">&rarr;</button>
</div>

您可以看到我留下了一些简单的按钮来实现移动,但它们还不能正常工作。如果您想按照我在这里所做的方式继续实现,您可以将元素渲染到可见框架之外的某个范围,然后实现某种动画重新定位。此处的 renderElements 函数仅渲染可见的内容,因此您可以使用类似的东西而不会陷入渲染无限元素的困境,即使您可以“滚动”多远没有理论上的限制。

【讨论】:

  • 这太棒了。我昨天想通了这个逻辑,尽管使用列表长度的四舍五入的平方根来进行 Y 位移也是一样的!我只是在玩 PixiJS,我有点难以实现,并想“让我们看看昨天发布的这个 stackoverflow”。它就在那里。惊人的。我当然需要玩,如果我设法把它拉出来,就会更新这个堆栈。非常感谢(我的 +1 还没有出现,因为我是新手,但它就在那里)
  • 这是一个很棒的问题!实现移动我可以看到两种方法:一种是将一些变量保持为“虚拟”左和上滚动,并更新元素的绝对位置以考虑。另一种可能是在框架和元素之间添加一个中间div...将元素定位在中间,中间在框架内的位置可以代表用户滚动。
  • 其实我两个都去啊哈!这很有用,因为我不能逐像素移动原点,而是通过“单元格”更新网格“单元格”
【解决方案2】:

@arbuthnott 我编辑了您的代码以通过递减 relativeX 和 relativeY 变量来实现探索。我还插入了一个“原点”div(1x1 px,溢出可见)。这个 DOM 元素将代表 X 和 Y 原点。我不确定它是否必不可少,但它真的很方便。

现在我的函数当前删除所有元素并在每次更新时重新插入所有元素(现在每 500 毫秒)。

我们的想法是找到一种方法来比较我需要的元素与已经存在的元素。 也许将现有元素存储到一个数组中,并将该数组与“查询”数组进行比较。而不是只看到缺少的元素。

这是一个想法,不确定实现(我不擅长处理数组)。

https://jsfiddle.net/bnv6mumd/64/

var sources = ['red','green','blue','cyan','orange','yellow','pink','purple'];

var frame = $('.frame'),
		origin = $('.origin');

var fWidth = 600,
		fHeight = 300,
   	srcTotal = sources.length,
    srcSquare = Math.ceil(Math.sqrt(srcTotal)),
    rX = 0,
    rY = 0;

var gridSize = 30,
		gutterSize = 5,
		elementSize = gridSize - gutterSize;


function getSourceItem(x,y) {
	var index = x + y * srcSquare;
  return sources[Math.abs(index) % srcTotal];
}

function getOffset(x,y) {
  return [gridSize * x,gridSize * y];
}

function drawElement(x,y) {
	var sourceItem = getSourceItem(x,y);
  var offsets = getOffset(x,y);
  var element = $('<div></div>').addClass('element').css({
  	left: offsets[0] + 'px',
    top: offsets[1] + 'px',
    'background-color': sourceItem,
  });
  origin.append(element);
}

function init() {
  var x = 0, y = 0;
  while ( gridSize * x < fWidth) {
    while ( gridSize * y < fHeight) {
    	drawElement(x,y);
      y++;
    }
    y = 0;
    x++;
  }
}

function updateElements() {
	origin.empty();
  var x = -Math.trunc(rX / gridSize) -1, y = - Math.trunc(rY / gridSize) -1;
  while ( gridSize * x + rX < fWidth) {
    while ( gridSize * y + rY < fHeight) {
    	drawElement(x,y);
      y++;
    }
    y = -Math.ceil(rY / gridSize);
    x++;
  }
}

function animate() {
  rX -= 5;
  rY -= 5;
  origin.css({left: rX, top: rY})
  updateElements();
  console.log("relative X : " + rX + " | relative Y : " + rY);
}

setInterval(animate, 500)
init();
.frame {
  border: 2px solid black;
  margin: 40px auto;
  height: 300px;
  width: 600px;
  position: relative;
  overflow: hidden;
}

.origin {
  height: 1px;
  width: 1px;
  position: absolute;
  overflow: visible;
}

.frame .element {
  position: absolute;
  width: 25px;
  height: 25px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
<div class="origin" style="top:0;left:0;"></div>
</div>

【讨论】:

  • 这个版本还可以看到集团如何出现和消失。我不知道为什么会有这种模式,但还不错jsfiddle.net/bnv6mumd/65
  • 我喜欢那个小提琴版本。只创建您需要的元素!我认为您可以适应这种情况而无需费力地监听拖动事件或按钮单击而不是自动动画。但是看起来真的很不错!
  • 是的,但我每帧都重新生成所有元素。删除旧的并只创建新的不是更好吗?还不知道如何
【解决方案3】:

这是我的最终 sn-p 版本(我现在将开始针对我的案例进行实际实现)。

我认为我以不错的方式优化了 DOM 操作、代码结构等(不过我非常愿意接受建议)。

我现在只更新需要更新的元素(点击框附近显示溢出)

https://jsfiddle.net/bnv6mumd/81/

var sources = ['red', 'green', 'blue', 'cyan', 'orange', 'yellow', 'pink', 'purple'];

var frame = $('.frame'),
    origin = $('.origin');

var srcTotal = sources.length,
    srcSquare = Math.round(Math.sqrt(srcTotal)),
    fWidth = 200,
    fHeight = 200,
    cellSize = 50,
    gutterSize = 20,
    gridSize = [Math.floor(fWidth / cellSize) + 1, Math.floor(fHeight / cellSize) + 1],
    aX = 0, // Absolute/Applied Coordinates
    aY = 0,
    rX = 0, // Relative/frame Coordinates
    rY = 0;

function getSrcItem(x, y) {
    var index = x + y * srcSquare;
    return sources[Math.abs(index) % srcTotal];
}

function getOffset(x, y) {
    return [cellSize * x, cellSize * y];
}

function getY() {
    return Math.floor(-rY / cellSize);
}

function getX() {
    return Math.floor(-rX / cellSize);
}

function drawElement(x, y) {
    var srcItem = getSrcItem(x, y),
        offsets = getOffset(x, y),
        element = $('<div></div>').addClass('element').css({
            left: offsets[0] + 'px',
            top: offsets[1] + 'px',
            'background-color': srcItem,
        }).attr({
            "X": x,
            "Y": y
        });
    origin.append(element);
}

function drawCol(x, y) {
    var maxY = y + gridSize[1];
    while (y <= maxY + 1) {
        drawElement(x - 1, y - 1);
        y++;
    }
}

function drawLign(x, y) {
    var maxX = x + gridSize[0];
    while (x <= maxX + 1) {
        drawElement(x - 1, y - 1);
        x++;
    }
}

function drawGrid() {
    origin.empty();
    var x = getX(),
        y = getY(),
        maxX = x + gridSize[0],
        maxY = y + gridSize[1];
    while (y <= maxY + 1) {
        drawLign(x, y);
        x = getX();
        y++;
    }
}

function updateX(x, y, diffX, diffY) {
    if (Math.sign(diffX) == -1) {
        drawCol(aX - 1, y);
        $('[x=' + (aX + gridSize[0]) + ']').remove();
        aX--;
    } else if (Math.sign(diffY) == 1) {
        drawCol(aX + gridSize[0] + 2, y);
        $('[x=' + (aX - 1) + ']').remove();
        aX++;
    }
}

function updateY(x, y, diffX, diffY) {
    if (Math.sign(diffY) == -1) {
        drawLign(x, aY - 1);
        $('[y=' + (aY + gridSize[0]) + ']').remove();
        aY--;
    } else if (Math.sign(diffY) == 1) {
        drawLign(x, aY + gridSize[0] + 2);
        $('[y=' + (aY - 1) + ']').remove();
        aY++;
    }
}

function animate() {
    rX += 1;
    rY += 1;
    origin.css({
        left: rX,
        top: rY
    });
    var x = getX(),
        y = getY(),
        diffX = x - aX,
        diffY = y - aY;
    if (diffX) {
        updateX(x, y, diffX, diffY)
    };
    if (diffY) {
        updateY(x, y, diffX, diffY)
    };
    requestAnimationFrame(animate);
}

$('body').click(function() {
$(frame).toggleClass("overflow");
})
drawGrid();
animate();
.frame {
    border: 2px solid black;
    margin: 100px auto;
    height: 200px;
    width: 200px;
    position: relative;
}

.overflow{
    overflow:hidden;
}

.origin {
    height: 1px;
    width: 1px;
    position: absolute;
    overflow: visible;
}

.frame .element {
    position: absolute;
    width: 30px;
    height: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame overflow">
    <div class="origin" style="top:0;left:0;"></div>
</div>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多