草庐IT

javascript - 如何计算连续 flexbox 元素的数量?

coder 2023-07-04 原文

网格是使用 CSS flexbox 实现的。 Example:



此示例中的行数为 4,因为我出于演示目的固定了容器宽度。但是,实际上,它可以根据容器的宽度而改变(例如,如果用户调整窗口大小)。尝试在 this example 中调整输出窗口的大小去感受一下。

总是有一个事件元素,用黑色边框标记。

使用 JavaScript,我允许用户使用左/右箭头导航到上一个/下一个元素。在我的实现中,我只是将事件项的索引减少/增加 1。

现在,我还想允许用户向上/向下导航。为此,我只需要通过 <amount of items in a row> 减少/增加事件元素的索引。 .但是,鉴于它取决于容器的宽度,我该如何计算这个数字?有没有更好的方法来实现向上/向下功能?

.grid {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  width: 250px;
  height: 200px;
  background-color: #ddd;
  padding: 10px 0 0 10px;
}

.item {
  width: 50px;
  height: 50px;
  background-color: red;
  margin: 0 10px 10px 0;
}

.active.item {
  outline: 5px solid black;
}
<div id="grid" class="grid">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item active"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

最佳答案

这个问题比查找一行中有多少元素稍微复杂一些。

最终,我们想知道事件元素的上方、下方、左侧和右侧是否有元素。这需要考虑底行不完整的情况。例如,在下面的情况下,事件元素没有上方、下方或右侧的元素:



但是,为了确定事件元素的上方/下方/左侧/右侧是否有元素,我们需要知道一行中有多少元素。

查找每行的元素数

要获得每行的元素数,我们需要:

  • itemWidth - outerWidth单个元素包括 border , paddingmargin
  • gridWidth - innerWidth网格,不包括 border , paddingmargin

  • 要使用纯 JavaScript 计算这两个值,我们可以使用:
    const itemStyle = singleItem.currentStyle || window.getComputedStyle(active);
    const itemWidth = singleItem.offsetWidth + parseFloat(itemStyle.marginLeft) + parseFloat(itemStyle.marginRight);
    
    const gridStyle = grid.currentStyle || window.getComputedStyle(grid);
    const gridWidth = grid.clientWidth - (parseFloat(gridStyle.paddingLeft) + parseFloat(gridStyle.paddingRight));
    

    然后我们可以使用以下方法计算每行的元素数:
    const numPerRow = Math.floor(gridWidth / itemWidth)
    

    注:这仅适用于统一尺寸的物品,并且仅适用于 marginpx 中定义单位。

    一个非常非常简单的方法

    处理所有这些宽度、填充、边距和边框真的很令人困惑。有一个非常非常简单的解决方案。

    我们只需要找到 offsetTop 的网格元素的索引即可。属性大于第一个网格元素的 offsetTop .
    const grid = Array.from(document.querySelector("#grid").children);
    const baseOffset = grid[0].offsetTop;
    const breakIndex = grid.findIndex(item => item.offsetTop > baseOffset);
    const numPerRow = (breakIndex === -1 ? grid.length : breakIndex);
    

    最后的三元考虑了网格中只有一个元素和/或单行元素的情况。

    const getNumPerRow = (selector) => {
      const grid = Array.from(document.querySelector(selector).children);
      const baseOffset = grid[0].offsetTop;
      const breakIndex = grid.findIndex(item => item.offsetTop > baseOffset);
      return (breakIndex === -1 ? grid.length : breakIndex);
    }
    .grid {
      display: flex;
      flex-wrap: wrap;
      align-content: flex-start;
      width: 400px;
      background-color: #ddd;
      padding: 10px 0 0 10px;
      margin-top: 5px;
      resize: horizontal;
      overflow: auto;
    }
    
    .item {
      width: 50px;
      height: 50px;
      background-color: red;
      margin: 0 10px 10px 0;
    }
    
    .active.item {
      outline: 5px solid black;
    }
    <button onclick="alert(getNumPerRow('#grid'))">Get Num Per Row</button>
    
    <div id="grid" class="grid">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item active"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>


    但是上面或下面有一个元素吗?

    要知道事件元素上方或下方是否有元素,我们需要知道 3 个参数:
  • totalItemsInGrid
  • activeIndex
  • numPerRow

  • 例如,在以下结构中:
    <div id="grid" class="grid">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item active"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>
    

    我们有一个 totalItemsInGrid5 , activeIndex有一个从零开始的索引 2 (它是组中的第三个元素),让我们说 numPerRow是 3。

    我们现在可以通过以下方式确定事件元素的上方、下方、左侧或右侧是否有元素:
  • isTopRow = activeIndex <= numPerRow - 1
  • isBottomRow = activeIndex >= totalItemsInGid - numPerRow
  • isLeftColumn = activeIndex % numPerRow === 0
  • isRightColumn = activeIndex % numPerRow === numPerRow - 1 || activeIndex === gridNum - 1

  • isTopRowtrue我们不能向上移动,如果isBottomRowtrue我们不能向下移动。如 isLeftColumntrue我们不能向左移动,如果 isRightColumn如果 true我们不能向右移动。

    备注 :isBottomRow不仅检查事件元素是否在底行,还会检查其下方是否有元素。在我们上面的例子中,事件元素是 不是 在底行,但下面没有元素。

    一个工作示例

    我已经把它变成了一个可以调整大小的完整示例 - 并制作了 #grid元素可调整大小,因此可以在下面的代码段中进行测试。

    我创建了一个函数,navigateGrid接受三个参数:
  • gridSelector - 网格元素的 DOM 选择器
  • activeClass - 事件元素的类名
  • direction - up之一, down , left , 或 right

  • 这可以用作 'navigateGrid("#grid", "active", "up")使用您问题中的 HTML 结构。

    该函数使用 offset 计算行数方法,然后检查是否 active元素可以更改为上/下/左/右元素。

    换句话说,该函数检查事件元素是否可以向上/向下和向左/向右移动。这意味着:
  • 不能从最左边的列向左走
  • 不能从最右边的列开始
  • 顶行上不去
  • 不能从底行向下,或者如果下面的单元格为空


  • const navigateGrid = (gridSelector, activeClass, direction) => {
      const grid = document.querySelector(gridSelector);
      const active = grid.querySelector(`.${activeClass}`);
      const activeIndex = Array.from(grid.children).indexOf(active);
    
      const gridChildren = Array.from(grid.children);
      const gridNum = gridChildren.length;
      const baseOffset = gridChildren[0].offsetTop;
      const breakIndex = gridChildren.findIndex(item => item.offsetTop > baseOffset);
      const numPerRow = (breakIndex === -1 ? gridNum : breakIndex);
    
      const updateActiveItem = (active, next, activeClass) => {
        active.classList.remove(activeClass);
        next.classList.add(activeClass); 
      }
      
      const isTopRow = activeIndex <= numPerRow - 1;
      const isBottomRow = activeIndex >= gridNum - numPerRow;
      const isLeftColumn = activeIndex % numPerRow === 0;
      const isRightColumn = activeIndex % numPerRow === numPerRow - 1 || activeIndex === gridNum - 1;
      
      switch (direction) {
        case "up":
          if (!isTopRow)
            updateActiveItem(active, gridChildren[activeIndex - numPerRow], activeClass);
          break;
        case "down":
          if (!isBottomRow)
            updateActiveItem(active, gridChildren[activeIndex + numPerRow], activeClass);
          break;  
        case "left":
          if (!isLeftColumn)
            updateActiveItem(active, gridChildren[activeIndex - 1], activeClass);
          break;   
        case "right":
          if (!isRightColumn)
            updateActiveItem(active, gridChildren[activeIndex + 1], activeClass);    
          break;
      }
    }
    .grid {
      display: flex;
      flex-wrap: wrap;
      align-content: flex-start;
      width: 400px;
      background-color: #ddd;
      padding: 10px 0 0 10px;
      margin-top: 5px;
      resize: horizontal;
      overflow: auto;
    }
    
    .item {
      width: 50px;
      height: 50px;
      background-color: red;
      margin: 0 10px 10px 0;
    }
    
    .active.item {
      outline: 5px solid black;
    }
    <button onClick='navigateGrid("#grid", "active", "up")'>Up</button>
    <button onClick='navigateGrid("#grid", "active", "down")'>Down</button>
    <button onClick='navigateGrid("#grid", "active", "left")'>Left</button>
    <button onClick='navigateGrid("#grid", "active", "right")'>Right</button>
    
    <div id="grid" class="grid">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item active"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>

    关于javascript - 如何计算连续 flexbox 元素的数量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49043684/

    有关javascript - 如何计算连续 flexbox 元素的数量?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    7. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

      我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

    8. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    9. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

      在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

    10. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

      我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

    随机推荐