最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why does calling focus() break my CSS transition? - Stack Overflow

programmeradmin4浏览0评论

This html file shows a 3 panel layout. A css transition on the "left" property gives me a nice sliding effect when I change panels. It all works but, when I change panels, I want to place the focus on an input in the new panel. Can anyone tell me why the call to focus() breaks the css transition, and lands me half way between two panels? It only happens going forward. It works if I comment out the transition, and it works if I delay the call to focus() with a timeout.

The problem occurs in Chrome, IE and 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>

This html file shows a 3 panel layout. A css transition on the "left" property gives me a nice sliding effect when I change panels. It all works but, when I change panels, I want to place the focus on an input in the new panel. Can anyone tell me why the call to focus() breaks the css transition, and lands me half way between two panels? It only happens going forward. It works if I comment out the transition, and it works if I delay the call to focus() with a timeout.

The problem occurs in Chrome, IE and 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>

Share Improve this question edited May 20, 2021 at 7:13 vsync 130k59 gold badges340 silver badges421 bronze badges asked Nov 6, 2014 at 15:21 bbsimonbbbbsimonbb 29k18 gold badges90 silver badges126 bronze badges 6
  • Can you provide a live demo for us to edit? – Zach Saucier Commented Nov 6, 2014 at 15:56
  • For whatever reason, my attempt to reproduce this as a jsfiddle didn't work. You can save the code locally, and open it in a browser from your file system. I don't have a web facing server handy sorry. – bbsimonbb Commented Nov 6, 2014 at 16:22
  • 1 This is not a total answer, but maybe useful... The browsers have rules for scrolling the active element into view. These rules have precedence over whatever behaviour you might try to script. In the above example, tabbing also caused me problems. The workaround for the moment is to manage focus at all times. Something on the page must always be focussed, and calls to focus() are made inside setTimeout() so transitions have a chance to complete. – bbsimonbb Commented Dec 5, 2014 at 8:38
  • Here's a working jsFiddle showing the issue. – Brett DeWoody Commented Feb 17, 2015 at 23:38
  • Thank you that shows the issue perfectly. I've changed the timeout to 300ms, and now the timeout version shows the desired behaviour. Tis a tricky one. – bbsimonbb Commented Feb 19, 2015 at 9:51
 |  Show 1 more comment

2 Answers 2

Reset to default 17

As @user1585345 correctly stated a browser immediately scrolls to the focused element in order to bring it inside the current view, and not a px further!

What happens here is the following: you change the relative position of your element, but immediately you change focus, since the focused elements when the event fires is not visible the content also scrolls (ending the animation abruptly).

You should explicitly fire your focus only when the animation is complete, so that the browser recognise that no scrolling is necessary.

One solution is (suggested by @Mark)

there are two event listeners 'ontransitionend' and 'onanimationend' you can add an event for and then fire your focus() function

As mentioned in another answer, focus() scrolls to the element and visually breaks any transition mid-flow.

If you didn't want to rely on listeners, and depending on your requirements, you could use the preventScroll: true flag when calling the focus() method.


input.focus({ preventScroll: true })

See the MDN documentation here and also check browser compatibility. Not all browsers support this but many of them do.

发布评论

评论列表(0)

  1. 暂无评论