为什么调用focus()会破坏我的CSS过渡效果?

13

这份 HTML 文件显示了一个三面板的布局。在“left”属性上的 CSS 过渡效果使我在更改面板时获得了一个不错的滑动效果。一切都正常,但是当我更改面板时,我想将焦点放在新面板中的一个输入框上。有人能告诉我为什么调用 focus() 会打断 CSS 过渡效果,并让我停留在两个面板之间吗?这只发生在前进时。如果我注释掉过渡,它可以正常工作,如果我延迟调用 focus(),它也可以正常工作。

这个问题出现在 Chrome、IE 和 Firefox 中。

var panelContainer;
var panels = new Array();
var numPanels;
var currentPanel = 0;

window.addEventListener('load', init, false);

function init() {
  setTimeout(function() {
    window.scrollTo(0, 1);
  }, 10); //hiding the browser address bar in iPhone/Android

  panelContainer = document.getElementById("panelContainer");
  panels = document.getElementsByClassName("panel");
  numPanels = panels.length;
  sizeResize();
}

function sizeResize() {
  panelContainer.style.width = (numPanels * window.innerWidth) + "px";
  panelContainer.style.height = window.innerHeight + "px";
  for (var i = 0; i < numPanels; i++) {
    panels[i].style.width = window.innerWidth + "px";
    panels[i].style.height = window.innerHeight + "px";
  }
  slide();
}
window.onresize = sizeResize;

function slide(panel) {
  if (panel != undefined)
    currentPanel = panel;
  panelContainer.style.left = -(window.innerWidth * currentPanel) + "px";
  if (panel >= 0) {
    panels[panel].getElementsByTagName("input")[0].focus(); //shows problem
    //window.setTimeout(function () { panels[panel].getElementsByTagName("input")[0].focus() }, 100);//no problem

  }
}
#wrapper {
  width: 100%;
  height: auto;
  overflow: hidden;
}

#panelContainer {
  position: relative;
  transition: left 1000ms ease;
}

.panel {
  float: left;
}
<div id="wrapper">
  <div id="panelContainer">
    <div id="panel0" class="panel" style="background-color: #888;">
      panel zero
      <input id="btn0" class="focussable" type="button" onclick="slide(1);" value="forward" />
    </div>
    <div id="panel1" class="panel" style="background-color: #AAA;">
      panel 1
      <input id="btn1" class="focussable" type="button" onclick="slide(2);" value="forward" />
      <input type="button" onclick="slide(0);" value="back" />
    </div>
    <div id="panel2" class="panel" style="background-color: #CCC;">
      panel 2
      <input id="btn2" class="focussable" type="button" onclick="slide(1);" value="back" />
    </div>
  </div>
</div>


你能提供一个实时演示给我们编辑吗? - Zach Saucier
由于某些原因,我尝试在jsfiddle上重现此代码失败了。您可以将代码保存在本地,并从文件系统中的浏览器中打开它。很抱歉,我没有一个面向网络的服务器。 - bbsimonbb
1
这不是完整的答案,但可能有用... 浏览器有滚动活动元素到视图的规则。这些规则优先于您尝试编写的任何行为。在上面的示例中,切换选项卡也给我带来了问题。目前的解决方法是始终管理焦点。页面上必须始终有焦点,并且在setTimeout()内调用focus(),以便转换有机会完成。 - bbsimonbb
这里有一个可工作的 jsFiddle,展示了这个问题。 - Brett DeWoody
谢谢,这完美地展示了问题。我已经将超时时间更改为300毫秒,现在超时版本显示了所需的行为。这是一个棘手的问题。 - bbsimonbb
也遇到这个问题了...这太奇怪了。 - jiyinyiyong
2个回答

14

正如@user1585345所正确指出的那样,浏览器会立即将焦点元素滚动到当前视图内,而不是多滚动一像素!

这里发生的情况是:您更改了元素的相对位置,但立即更改焦点(因为事件触发时聚焦的元素不可见),导致内容也被滚动(动画突然结束)。

您应该在动画完成后显式地触发焦点,以便浏览器认识到不需要滚动。

一种解决方法是(由@Mark建议)

有两个事件监听器'ontransitionend'和'onanimationend',您可以添加一个事件,然后触发focus()函数


2
这让我免于三年后的疯狂。但是这很麻烦,因为它意味着你必须使用超时并希望如果你使用CSS转换,或者切换到脚本动画,以便知道它们何时完成。 - Whelkaholism
2
错误,有两个事件监听器'ontransitionend'和'onanimationend'可供添加事件,然后触发您的focus()函数。 - Mark
这是实现我建议的一种方式。我不明白这怎么会使我的答案错误。您可以详细说明一下,以便我们可以用更多的细节来修复答案吗? - Kuzeko
马克,谢谢你!我需要学习ontransitionend! - R Claven

1
如同另一个答案中提到的,focus() 方法会滚动到元素处并在过渡中可视地中断。
如果您不想依赖监听器,并根据您的需求,您可以在调用 focus() 方法时使用 preventScroll: true 标志。

input.focus({ preventScroll: true })

请查看MDN文档此处,并检查浏览器兼容性。并非所有浏览器都支持此功能,但其中许多浏览器都支持。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接