javascript - 如何让听者在手机屏幕上拉下或拉下时始终工作?

标签 javascript mobile bootstrap-4

我正在开发一个小图书馆来进行心理实验。您可以现场试用here并尝试以下代码段中的代码。

var selfConcept = (function() {
  //privates    
  var activeListenerStepNames = [];
  var accuracyFeedbackDuration = 400;
  var blankInterval = 1500;
  var clickListenerHandler = function(e) {
    buttonTouched = '';

    switch (e.target.id) {
      case 'd-button':
        buttonTouched = 'D';
        break;
      case 'k-button':
        buttonTouched = 'K';
        break;
    }

    e.data = buttonTouched;

    window.performance.mark(markName);
    window.performance.measure(measureId, generateMarkName('start'), markName);

    responseTimes.push({
      'stepId': currentStep,
      'stimulus': steps[currentStep]['stimulus'],
      'responseTime': window.performance.getEntriesByName(measureId)[0]["duration"],
      'key': e.data
    });

    //fix for Android
    document.getElementById("hidden-input").value = '';

    drawSetting();
    nextStep();
  };
  var currentStep = 0;
  var fixationCrossDuration = 1000;
  var frameId;
  var keyListenerHandler = function(e) {

    if (String.fromCharCode(e.keyCode) == 'D' || String.fromCharCode(e.keyCode) == 'K') {
      window.performance.mark(markName);
      window.performance.measure(measureId, generateMarkName('start'), markName);

      responseTimes.push({
        'stepId': currentStep,
        'stimulus': steps[currentStep]['stimulus'],
        'responseTime': window.performance.getEntriesByName(measureId)[0]["duration"],
        'key': String.fromCharCode(e.keyCode),
      });

      isAccuracyFeedbackDisplayed = false;
      removeListener(window, 'input', markName, keyListenerHandler);

      nextStep();
    }
  };
  var spaceListenerHandler = function(e) {
    if (String.fromCharCode(e.keyCode) == ' ') {
      e.preventDefault();
      removeListener(document, "keydown", markName, spaceListenerHandler);
      drawSetting();
      nextStep();
    }
  }
  var isAccuracyFeedbackDisplayed = false;
  var measureId;
  var responseTimes = [];
  var steps = [];
  var groupInstruction;

  function addListener(element, event, name, eventFunction) {
    element.addEventListener(event, eventFunction);
    activeListenerStepNames.push(name);

    //console.log(activeListenerStepNames);
  }

  function drawSetting(text, color, background) {
    text = (text === undefined) ? '' : text;
    color = (color === undefined) ? 'black' : color;
    background = (background === undefined) ? 'white' : background;

    workAreaDiv = document.getElementById("work-area");
    workAreaDiv.innerHTML = "";

    div = document.createElement('div');

    div.id = 'stimulus';
    div.style.color = color;
    div.innerHTML = text;
    div.style.fontSize = '280%';

    div.style.class = 'col';

    workAreaDiv.appendChild(div);
  }

  function fixForMobilePhones() {
    $('#work-area-container').removeClass('h-100').addClass('h-75');
    $('#container').append('<div class="row h-25"><div id="d-button" style="background-color: black; color: white; border-right: 1px solid white;" class="col-6 text-center"><h1>NO</h1></div><div id="k-button" style="background-color: black; color: white;" class="col-6 text-center"><h1>YES</h1></div></div>');

    addListener(document.getElementById("d-button"), 'click', markName, clickListenerHandler);
    addListener(document.getElementById("k-button"), 'click', markName, clickListenerHandler);
  }

  function generateMarkName(name) {
    return name + '-' + steps[currentStep]["type"] + '-' + currentStep;
  }

  function isMobile() {
    if (navigator.userAgent.match(/Android/i) ||
      navigator.userAgent.match(/webOS/i) ||
      navigator.userAgent.match(/iPhone/i) ||
      navigator.userAgent.match(/iPad/i) ||
      navigator.userAgent.match(/iPod/i) ||
      navigator.userAgent.match(/BlackBerry/i) ||
      navigator.userAgent.match(/Windows Phone/i)
    ) {
      return true;
    }

    return false;
  }

  function nextStep() {
    var nextStep = currentStep + 1;

    if (nextStep in steps) {
      currentStep = nextStep;
      markName = generateMarkName('start');
      window.performance.mark(markName);
      //console.log("mark - markName:"+markName);
    } else {
      window.cancelAnimationFrame(frameId);
      frameId = undefined;

      alert('end');
    }
  }

  function randomizeSteps() {
    words = ["word1", "word2", "word3", "word4", "word5", "word6", "word7", "word8", "word9", "word10"];
    trials = [];

    index = 0;
    for (index = 0; index < words.length; index++) {
      trials.push({
        'id': index,
        'type': 'trial',
        'stimulus': words[index]
      });
    }

    instructions = [

      {
        'type': 'duration',
        'stimulus': '',
        'duration': blankInterval
      },
      {
        'type': 'duration',
        'stimulus': '+',
        'color': 'black',
        'duration': fixationCrossDuration
      },
      {
        'type': 'duration',
        'stimulus': '',
        'duration': blankInterval
      }
    ];

    trials = shuffleArray(trials);

    trialsWithBlankInterval = [];

    for (itemIndex = 1; itemIndex < trials.length; itemIndex++) {
      trialsWithBlankInterval.push(trials[itemIndex]);
      trialsWithBlankInterval.push({
        'type': 'duration',
        'stimulus': '',
        'duration': fixationCrossDuration
      });
    }

    steps = instructions.concat(trialsWithBlankInterval);
  }

  function removeListener(element, event, name, eventFunction) {
    element.removeEventListener(event, eventFunction);
    activeListenerStepNames.splice(activeListenerStepNames.indexOf(name), 1);
  }

  /* function copied from https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array */
  function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]];
    }

    return array;
  }

  function startTimer() {
    frameId = requestAnimationFrame(startTimer);
    window.performance.mark('frame');
    measureId = 'measure-' + currentStep;

    // console.log("TYPE:"+steps[currentStep]["type"]);

    if (isMobile() == true && $('#work-area-container').hasClass('h-100')) {
      fixForMobilePhones();
    }

    switch (steps[currentStep]["type"]) {
      case 'instructions':
        document.getElementById("work-area").classList.remove('text-center');
        document.getElementById("work-area").classList.add('text-justify');

        document.getElementById("work-area").innerHTML = steps[currentStep]["html"];

        if (activeListenerStepNames.indexOf(markName) == -1) {
          addListener(document, 'keydown', markName, spaceListenerHandler);
        }

        break;

      case 'duration':
        document.getElementById("work-area").classList.remove('text-justify');
        document.getElementById("work-area").classList.add('text-center');
        drawSetting(steps[currentStep]["stimulus"]);

        window.performance.measure(measureId, generateMarkName('start'), 'frame');
        performanceEntries = window.performance.getEntriesByName(measureId);

        var max = 0;
        for (var i = 0; i < performanceEntries.length; i++) {
          if (parseInt(performanceEntries[i]["duration"]) > max)
            max = performanceEntries[i]["duration"];
        }

        if (max >= steps[currentStep]["duration"]) {
          //console.log('step: ' + currentStep);
          //console.log(performanceEntries[performanceEntries.length - 1]["duration"]);
          nextStep();
        }

        break;

      case 'trial':
        document.getElementById("work-area").classList.remove('text-justify');
        document.getElementById("work-area").classList.add('text-center');
        drawSetting(steps[currentStep]["stimulus"], steps[currentStep]["color"], steps[currentStep]["background"]);

        markName = generateMarkName('response');

        if (activeListenerStepNames.indexOf(markName) == -1) {
          //console.log(activeListenerStepNames);
          addListener(document, 'keydown', markName, keyListenerHandler);
        }

        break;
    }
  }

  //public
  return {
    init: function() {
      randomizeSteps();

      markName = generateMarkName('start');
      window.performance.mark(markName);
      console.log("INIT");
      startTimer();
    }
  }
})();

selfConcept.init();
html,
body {
  height: 100%;
  font-size: 100%;
}

.container {
  height: 100%;
}

input.transparent {
  opacity: 0;
  filter: alpha(opacity=0);
}

.text-overflow {
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

.likert .row>.col,
.likert .row>[class^="col-"] {
  padding-top: .75rem;
  padding-bottom: .75rem;
  background-color: rgba(86, 61, 124, .15);
  border: 1px solid rgba(86, 61, 124, .2);
}
<div id="container" class="container">

  <div id="work-area-container" class="row h-100 text-overflow">
    <div class="col my-auto">
      <div id="work-area" class="w-100 mx-auto text-justify"></div>
      <input id="hidden-input" class="transparent" type="text" readonly="readonly">
    </div>
  </div>

</div>

用户必须根据两个或多个选项(在示例中:“否”和"is")对出现在屏幕中间的刺激进行分类。我必须以毫秒为单位测量从刺激出现的那一刻到用户键盘(或点击手机)上的按键的延迟。
为了实现这个目标,我使用了 Web API,特别是 Performance目的。此外,我使用 Bootstrap4 使其响应。台式机或笔记本电脑上的浏览器一切正常:用户可以使用键 D(意思是“否”)和 K("is")对刺激使用react。
我向您求助的问题只发生在手机上,手机的应答模式基于两个可见按钮:“否”和"is"。我注意到,当我无意中向上或向下滑动屏幕(尤其是多次这样做)时,听众不再起作用。就像窗口失去焦点一样,所以我必须在按钮上点击两次才能使它们工作(我想第一次再次获得焦点,第二次触发事件)破坏了延迟时间的测量。
我试图通过以下方式解决这个问题,但它不起作用:
document.addEventListener('touchstart', this.touchstart);
document.addEventListener('touchmove', this.touchmove);

function touchstart(e) {
    e.preventDefault();
}

function touchmove(e) {
    e.preventDefault();
}
编辑:
我还尝试了以下受 TheMindVirus's suggestion 启发的修复程序
var lastScrollPosition = 0;
window.onscroll = function(event)
{
    if((document.body.scrollTop >= 0) && (lastScrollPosition < 0))
    {
        // first try
        window.focus();

        // second try after having assigned tabindex='1' to the div "work-area-container"
       $('#work-area-container').focus();
    }
    
    lastScrollPosition = document.body.scrollTop;
}
我该如何解决问题?非常感谢。

最佳答案

selfconcept.js 中的事件监听器和处理程序看起来不错,但是代码片段中的两个是错误的。必须有普通的函数调用:

document.addEventListener('touchstart', touchstart);
document.addEventListener('touchmove', touchmove);
您可以在浏览器控制台中看到文件名和行号的错误:
test.html:51
[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. 
这意味着“这个”在这里不起作用......
我测试了你的测试页:
Galaxy S4 (Android 5):
  • 歌剧迷你(作品)
  • firefox(作品)
  • Chrome (白屏)
  • 三星浏览器(白屏)

  • Galaxy S7 (Android 7):
  • firefox(作品)
  • Chrome (作品)
  • 三星浏览器(作品)

  • iPhone 4s (IOS 9.3.5)
  • safari(白屏)

  • 我认为问题很可能不是您的代码,而是您的手机或浏览器...

    关于javascript - 如何让听者在手机屏幕上拉下或拉下时始终工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66934246/

    相关文章:

    javascript - 使用html更改鼠标单击表格单元格的颜色

    html - 防止缩放移动网站不起作用

    iphone - Web 应用程序 GPS 轮询

    html - 调整浏览器窗口大小时如何缩小所有 HTML 元素

    javascript - 如何控制溢出滚动的滚动条

    javascript - 在 Three.js 中定位网格

    javascript - 保留 html o/p 中的空格

    android - 在桌面上查看移动网站

    html - 垂直居中的 Bootstrap 容器问题

    html - 如何使用 bootstrap 4 调整卡片组内图像的大小?