在触摸屏上滚动会大大减慢Ajax下载时间



我看到了一些奇怪的行为,只要用手指伸出手指时,ajax请求在滚动时就挂在旁边。很难用文字来描述这个问题,因此请看一下这个小提琴:

$("div").on('scroll', infiniteDictionaryScrollAjax);
function infiniteDictionaryScrollAjax(){
$("div").off("scroll");
$.ajax({
       type: "POST",
       url : "/someURL",
       data: {data: "data"}
     })
     .done(function(response) {
       $("div").append("<br>appendedData");
       $("div").on('scroll', infiniteDictionaryScrollAjax);
     })
     .fail(function() {
       $("div").append("<br>appendedError");
       $("div").on('scroll', infiniteDictionaryScrollAjax);
     });
  }

https://jsfiddle.net/rx1qj9l5/5/

它发出了AJAX请求(向不存在的URL)并收回错误。AJAX请求在scroll事件上发出。当您使用鼠标滚动时,平均时间为80-200毫秒。这似乎非常稳定。

这是麻烦到达的地方。当用手指滚动时,ajax请求可能会陷入"下载内容" >直到手指从滚动中释放出来!

>

使用以下步骤,我能够始终繁殖:

  1. 在启用触摸的设备(在Google Chrome中)
  2. 在上面的页面上重新加载
  3. 打开Chrome Dev工具,转到网络选项卡
  4. 降落可滚动的Div(不要放手!)握住手指时上下滚动。
  5. 这将发射3个Ajax呼叫,其中三分之一将无限地悬挂在您释放手指之前。这只有在Google Chrome内的手指滚动时才会发生。
  6. 您释放手指的瞬间,您会看到Ajax经历了。

我想不出任何原因的原因。有人遇到过吗?这个问题会有任何解决方法吗?

P.S。 - 我尝试在Vanilla JavaScript中进行相同的绑定。没有差异。:/

好吧,我找到了这种行为的原因。由于默认情况下的JavaScript是单线程,在滚动启用触摸设备时,该线程被暂停。(我觉得要禁用"事件的性质"应该解决该问题,但是该线程仍在仍然停了下来)

但是,有一个解决方案。如果您产生HTML5 Webworker,则 JavaScript不再是单个螺纹。Webworker获得了自己的线程,并且滚动不再引起任何问题。

在网络工作者中使用Ajax进行一些我自己的测试,Ajax始终如一地更快,并且可以完美地滚动。这是我见过的最顺畅的无限卷轴!

通过网络工作者的Ajax似乎在周围工作得更好(可能是因为它具有自己的专用线程吗?)但是,对于移动铬滚动,并在滚动后继续保持1-2秒的暂停。使用Web Worker允许Ajax在滚动甚至停止之前完成,因此它无限期地给出了无缝滚动的幻想。

这是我(依赖的免费)使用Web Worker调用AJAX函数的解决方案:

主页的JS中的功能:

// wajax (webworker ajax)
function wajax(obj)
{
  var sendObj = {};
  sendObj.url  = obj.url;
  sendObj.data = obj.data;
  sendObj.csrf = {{csrfToken here}}; //make sure to fill this out if you're using csrf tokens

  if (typeof(Worker) !== "undefined") {
    if (typeof(ajaxWorker) === "undefined") {
      ajaxWorker = new Worker("path/to/webworker.js");
    }

    ajaxWorker.postMessage(JSON.stringify(sendObj));
    ajaxWorker.onmessage = function(event) {
      obj.success(event.data);
    };
  }
  else {
    // This means webworkers aren't available. Here, just do a regular ajax call....
  }
}

WebWorker(与页面的JavaScript与单独的脚本/线程):

Webworker具有我从jQuery源代码派生的代码,以允许在AJAX调用中使用JSON对象。我将源代码删除至最低限度。缩小之前的全尺寸网络工程师仅约80行。

/* ADAPTED FROM JQUERY SOURCE */
function param(a)
{
  var prefix,
      params = [],
      add    = function(key, value) {
        params[params.length] = encodeURIComponent(key) + "=" + encodeURIComponent(value == null ? "" : value);
      };
  for (prefix in a) {
    buildParams(prefix, a[prefix], add);
  }
  // Return the resulting serialization
  return params.join("&");
}
function buildParams(mainKey, mainValue, add)
{
  var name;
  var length = mainValue.length;
  if (Array.isArray(mainValue)) {
    for (var index = 0; index < length; index++) {
      var value = mainKey[index];
      if (/[]$/.test(mainKey)) {
        add(mainKey, value);
      }
      else {
        buildParams(mainKey + "[" + (typeof value === "object" && value != null ? index : "") + "]", value, add);
      }
    }
  }
  else if (typeof mainValue === "object") {
    for (name in mainValue) {
      buildParams(mainKey + "[" + name + "]", mainValue[name], add);
    }
  }
  else {
    add(mainKey, mainValue);
  }
}
/* END CODE ADAPTED FROM JQUERY SOURCE */
/*
* Created by Skeets 2017-12-13
* */
onmessage = function(e) {
  var obj = JSON.parse(e.data);
  obj.data._token = obj.csrf;
  var request = new XMLHttpRequest();
  request.open('POST', obj.url, true);
  request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
  request.onload = function() {
    if (request.status >= 200 && request.status < 400) {
      // Success
      var output;
      try {
        output = JSON.parse(request.responseText);
      }
      catch (e) {
        output = request.responseText;
      }
      postMessage(output);
    }
    else {
      // error
      console.log(request.responseText);
    }
  };
  request.onerror = function() {
    // connection error
  };
  request.send(param(obj.data));
};

一旦到位,您就可以这样做一个Ajax请求:

wajax({
  url    : "/some/url",
  data   : {value_a:"somestuff",value_b:2},
  success: function(response) {
    // do something with the response
  }
});

随意使用并适应此代码。

我想不出这种行为的任何原因。

最明显的是减少用户滚动时运行的JavaScript量,以使滚动更加顺畅。当JavaScript运行时,它会阻止UI更新,这可能会导致一些Janky滚动(尤其是如果有一些重型DOM操纵)。

有人遇到过吗?

我发现有人提交的铬问题听起来像同一问题:settimeout和xhr请求在用户滚动时被忽略。看起来由于存在不确定性而关闭。

这个问题会有任何解决方法吗?

可能不是。

当用户在页面底部时,您需要触发ajax

$(window).scroll(function() {
   if($(window).scrollTop() + $(window).height() == $(document).height()) {
       $.ajax({
       type: "POST",
       url : "/someURL",
       data: {data: "data"}
     })
     .done(function(response) {
       $("div").append("<br>appendedData");
     })
     .fail(function() {
       $("div").append("<br>appendedError");
     });
   }
});

可能值得研究:

用被动事件听众改善滚动性能

当您滚动页面时,延迟了,该页面不会锚定在您的手指上,这就是所谓的滚动Jank。很多时候,当您遇到卷轴jank时,罪魁祸首是一个触摸事件的听众。Chrome 51的新手被动活动听众是新兴的Web标准,可为滚动性能提供主要的潜在增强性,尤其是在移动设备上。

https://developers.google.com/web/updates/2016/06/passive-event-listeners

行为取决于您的计算机。看到您频繁地呼叫时,您正在耗尽系统的资源。对于使用低端设备的移动用户来说,这尤其令人讨厌。

在我的笔记本电脑上,每次滚动时,Ajax请求都会发射。但是,我确实很快就遭受了以下违规行为:

[违规]执行JavaScript时强制回转

这是由于您如此频繁地修改DOM的事实。请参阅强制布局/反流和布局thrashing

上的这篇文章

您想做的是限制您要为每次滚动处理的代码。您可以通过 debouncing 进行此操作(又称函数执行的速率)。

您可以将具有此内置的Lodash之类的库导入,但是如果这是您唯一需要的地方,那可能会过高。幸运的是,编写我们自己的(基本)debounce功能相对无痛:

function debounce(func, timeToWait) {
    var timeout;
    return function() {
        clearTimeout(timeout);
        timeout = setTimeout(func, timeToWait);
    };
}

在问题的上下文中,您可以使用debounce来限制infiniteDictionaryScrollAjax的执行率:

var debouncedAjax = debounce(infiniteDictionaryScrollAjax, 500); // debounce here
$("div").on('scroll', debouncedAjax); // use debounced
function infiniteDictionaryScrollAjax() {
    $("div").off("scroll");
    $.ajax({
            type: "POST",
            url: "/someURL",
            data: {
                data: "data"
            }
        })
        .done(function(response) {
            $("div").append("<br>appendedData");
            $("div").on('scroll', debouncedAjax); // use debounced
        })
        .fail(function() {
            $("div").append("<br>appendedError");
            $("div").on('scroll', debouncedAjax); // use debounced
        });
}
function debounce(func, timeToWait) {
    var timeout;
    return function() {
        clearTimeout(timeout);
        timeout = setTimeout(func, timeToWait);
    };
}

在此处查看工作JSFIDDLE。请注意,AJAX调用仅在用户暂停滚动后仅运行500毫秒(对于移动设备来说是完全合理的,因为用户将短暂暂停以在滚动时查看内容)。

最新更新