使用箭头键和智能延迟加载实现有机列表浏览

Squ*_*Cat 6 javascript jquery timeout settimeout

简单的场景

我有一个列表,我使用箭头键(向上,向下)实现浏览,并且在当前列表项的每次更改时,数据库对象通过AJAX加载.

甜.

问题

当用户非常快速地浏览列表时,我不希望每个请求都消失.但当然,原始请求应立即生效.

我的想法是使用变量作为延迟设置超时,并在项目的初始加载后,增加该变量.

这是有效的,但当用户停止浏览一小段时间,但随后继续,我仍然不希望每个请求都关闭.

所以我想,每次浏览事件都必须合理地增加延迟变量,直到达到阈值.

这种有机方法可以成功地减少不必要的物品装载量.

我的解决方案

我走了很远.这段代码(下面的解释)将完成这项工作,其中一个主要原因是:

在第一次浏览完成然后停止后,延迟将自动保持在(第二步)最小值150ms.

当然我试图解决这个问题,但正如你将看到的,这是一个有趣但可能相当常见的逻辑问题 - 我认为我的整体方法是错误的.

但我不知道如何.大脑无法计算.电脑说没有.

您可以筛选我给出的示例,也可以在jsFiddle中找到一个功能齐全的模拟器.

如果你选择jsFiddle:

单击按钮,立即显示项目加载.现在稍等一下,再次单击该按钮,初始加载将被延迟.如果您持续快速按下按钮,则只有在您完成点击过程后才会显示项目加载.

代码示例

我们在一个对象字面内,只是你知道.

_clickTimer: false,                     // holds what setTimeout() returns
_timerInc: 0,                           // the your timer delay are belong to us

/**
 * Function is triggered whenever the user hits an arrow key
 * itemRef is the passed list item object (table row, in this case)
 */

triggerItemClick: function(itemRef){

    var that=this;
    var itemId=$(itemRef).data('id');   // Get the item id

    if(this._clickTimer){               // If a timeout is waiting

        clearTimeout(this._clickTimer); // we clear it
        this._itemClickTimer=false;     // and reset the variable to false

        /**
         * Note that we only arrive here after the first call
         * because this._clickTimer will be false on first run
         */

        if(this._timerInc == 0){        // If our timer is zero
            this._timerInc = 150;       // we set it to 150
        } else {                        // otherwise
            if(this._timerInc <= 350)   // we check if it is lower than 350 (this is our threshold)
                this._timerInc += 15;   // and if so, we increase in steps of 15
        }

    } 

    /**
     * Regardless of any timing issues, we always want the list
     * to respond to browsing (even if we're not loading an item.
     */

    this.toggleListItem(itemId);

    /**
     * Here we now set the timeout and assign it to this._clickTimer
     */

    this._clickTimer=setTimeout(function(){

        // we now perform the actual loading of the item
        that.selectItem(itemId);

        // and we reset our delay to zero
        that._timerInc=0;

    }, this._timerInc); // we use the delay for setTimeout()

}
Run Code Online (Sandbox Code Playgroud)

说明

在第一次通话:_clickTimerfalse,_timerInc0的,所以第一次调用会导致延迟0setTimeout()_clickTimer将被设置.该项目将立即加载.

第二次调用 - 假设我们的超时仍在等待触发,则_clickTimer清除,延迟设置为150if 0或者15如果低于350(阈值)则增加.

如果你继续浏览,这很好用.计时器增加,只有在您停止浏览一段时间后才会触发加载.

但是你停止后,下次你继续的时候,_clickTimer不会是假的(因为setTimeout()一个计数器分配给它),这样反过来_timerInc会被设置为150的时候了.因此,第一次浏览将导致在加载任何内容之前延迟150ms.

叫我疯狂或挑剔,但目标是没有这种延迟.

当然你会说:simple,_clickTimersetTimeout()闭包结束时设置为false ,因此一旦浏览完成并且项目被加载就会重置.太好了,但这会导致延迟永远不会超过0ms.想一想,你会看到.

我希望这个问题得到了恰当的解释,并且有人的大脑比我的更有能力找到解决方案.

Jus*_*ent 2

使用Promises可能可以以非常复杂的方式做到这一点。由于这主要是糖衣,但我认为一定可以直接解决这个问题,我想我做到了。

更新了小提琴。我在文本中添加了延迟,这样我更容易调试东西,并且还做了一些小的整理,但我的实际更改非常小。下面对此进行详细介绍。

你在最后的评论是我的第一直觉:

当然你会说:简单,_clickTimer在关闭结束时设置为 false setTimeout(),这样一旦浏览完成并加载项目后它就会重置。很好,但这会导致延迟永远不会超过 0 毫秒。

事实上,这将使延迟永远不会超过 0,因为我们不能那么快地点击(或在实际应用程序中浏览那么快)。但是...如果我们只在延迟不为 0 时才重置怎么办?因此,如果超时发生,但仅在 0 毫秒后发生,我们就会记住发生了超时。如果晚于该时间,则一定发生了浏览的实际暂停。通过在超时回调中添加几行,可以轻松实现这一点,如下所示。

this._clickTimer = setTimeout(function() {

  // we now perform the actual loading of the item
  that.selectItem();

  // and we reset our delay to zero
  if (that._timerInc > 0) {
    that._clickTimer = false;
  }
  that._timerInc = 0;

}, this._timerInc); // we use the delay for setTimeout()
Run Code Online (Sandbox Code Playgroud)

它似乎完全按照你想要的方式工作,除了现在,如果你在点击之间等待足够长的时间,延迟将是 0 毫秒,然后是 150 毫秒,然后是 0 毫秒,等等。这可以通过添加额外的超时来解决,以防延迟0 毫秒,但仍会重置延迟。每当触发发生时(在演示中单击,在应用程序中浏览),此超时就会被取消。

我相信,这一切都会让一切按照你想要的方式运作。为了完整起见,我还在此处包含上述小提琴作为片段。

this._clickTimer = setTimeout(function() {

  // we now perform the actual loading of the item
  that.selectItem();

  // and we reset our delay to zero
  if (that._timerInc > 0) {
    that._clickTimer = false;
  }
  that._timerInc = 0;

}, this._timerInc); // we use the delay for setTimeout()
Run Code Online (Sandbox Code Playgroud)
var _simulator = {

  _clickTimer: false, // holds what setTimeout() returns
  _cancelClickTimer: false,
  _timerInc: 0, // the your timer delay are belong to us

  /**
   * Function is triggered whenever the user hits an arrow key
   * itemRef is the passed list item object (table row, in this case)
   */
  triggerItemClick: function() {

    var that = this;
    
    // always cancel resetting the timing, it can never hurt
    clearTimeout(that._cancelClickTimer);
    that._cancelClickTimer = false;

    if (this._clickTimer) { // If a timeout is waiting
      clearTimeout(this._clickTimer); // we clear it
      this._clickTimer = false; // and reset the variable to false

      /**
       * Note that we only arrive here after the first call
       * because this._clickTimer will be false on first run
       */
      if (this._timerInc == 0) { // If our timer is zero
        this._timerInc = 150; // we set it to 150
      } else { // otherwise
        if (this._timerInc <= 350) // we check if it is lower than 350 (this is our threshold)
          this._timerInc += 15; // and if so, we increase in steps of 15
      }
    }

    /**
     * Regardless of any timing issues, we always want the list
     * to respond to browsing (even if we're not loading an item.
     */
    this.toggleListItem();

    /**
     * Here we now set the timeout and assign it to this._clickTimer
     */
    this._clickTimer = setTimeout(function() {

      // we now perform the actual loading of the item
      that.selectItem();

      // and we reset our delay to zero
      if (that._timerInc > 0) {
      	that._clickTimer = false;
      } else {
      	that._cancelClickTimer = setTimeout(function() {
        	that._clickTimer = false;
        }, 150);
      }
      that._timerInc = 0;

    }, this._timerInc); // we use the delay for setTimeout()

  },

  /** the following functions are irrelevant for the problemsolving above **/

  toggleListItem: function() {
    $('#status').prepend($('<div />').text('You toggled a list item ... in ' + this._timerInc + ' ms'));
  },

  selectItem: function(id) {
    $('#loader').show();
    setTimeout(function() {
      $('#loader').hide();
    }, 800);
  }

};

$('#clickZone').on('click', function() {
  _simulator.triggerItemClick();
});
Run Code Online (Sandbox Code Playgroud)
#clickZone {
  background: #369;
  color: #fff;
  width: 420px;
  height: 80px;
  text-align: center;
  line-height: 80px;
  cursor: pointer;
  -ms-user-select: none;
  -moz-user-select: -moz-none;
  -webkit-user-select: none;
  user-select: none;
  font-family: Arial;
}

#status {
  line-height: 20px;
  margin-top: 10px;
  font-family: Arial;
  font-size: 12px;
  background: #936;
  color: #fff;
  padding: 7px 10px;
}

#status > div {
  padding: 2px 0 4px;
  border-bottom: 1px dashed #ddd;
}

#status > div:last-child {
  border-bottom: 0;
}

#loader,
#notice {
  display: none;
  margin-top: 10px;
  width: 320px;
  padding: 10px 15px;
  background: #ddd;
  font-family: Arial;
  font-size: 11px;
  text-align: center;
}

#notice {
  background: lightblue;
  font-size: 14px;
  color: #333;
}
Run Code Online (Sandbox Code Playgroud)