Skip to content

Instantly share code, notes, and snippets.

@remybach
Forked from hsablonniere/README.md
Last active January 10, 2016 02:20
Show Gist options
  • Save remybach/5957467 to your computer and use it in GitHub Desktop.
Save remybach/5957467 to your computer and use it in GitHub Desktop.

scrollIntoViewIfNeeded 4 everyone!!!

This gist provides a simple JavaScript implementation of the non-standard WebKit method scrollIntoViewIfNeeded that can be called on DOM elements.

Usage

Just use the code in index.js in your app or website. You can see usage in the test page test.html.

The parent element will only scroll if the element being called is out of the view. The boolean can force the element to be centered in the scrolling area.

Improvements

  • This code doesn't handle well cases where the element has position:absolute (and maybe other CSS modifications).

?/!

If you have any ideas to improve this code or if you think the WebKit implementation doesn't work like this, feel free to start a discussion...

Hubert SABLONNIÈRE @hsablonniere

if (!Element.prototype.scrollIntoViewIfNeeded) {
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded, bubbleUp, parent) {
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = parent || this.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom,
hasScrolled = false;
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
hasScrolled = true;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
hasScrolled = true;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
hasScrolled = true;
}
if (!hasScrolled && bubbleUp) {
this.scrollIntoViewIfNeeded.call(this, centerIfNeeded, bubbleUp, parent.parentNode);
}
};
}
<!DOCTYPE html>
<title>scrollIntoViewIfNeeded test page</title>
<style type="text/css">
body {
font: 14px Arial;
}
#scroll-area {
border: 1px solid #AAA;
height: 7em;
margin: 0;
overflow: auto;
padding: 0;
width: 400px;
white-space: nowrap;
}
#scroll-area li {
background: #EEE;
border-radius: 5px;
display: inline-block;
list-style: none;
padding: 5px 10px;
}
#scroll-area li:nth-child(2n+1) {
background: #DDD;
text-align: right;
}
#scroll-area li.selected {
background: #BADA55;
}
</style>
<h1>scrollIntoViewIfNeeded test page</h1>
<ul id="scroll-area"><li class="selected">item #0</li><li>item #1</li><li>item #2</li><li>item #3</li><li>item #4</li><li>item #5</li><li>item #6</li><li>item #7</li><li>item #8</li><li>item #9</li><li>item #10</li><li>item #11</li><li>item #12</li><li>item #13</li><li>item #14</li><li>item #15</li><li>item #16</li><li>item #17</li><li>item #18</li><li>item #19</li><li>item #20</li><li>item #21</li><li>item #22</li><li>item #23</li><li>item #24</li><li>item #25</li><li>item #26</li><li>item #27</li><li>item #28</li><li>item #29</li><li>item #30</li><li>item #31</li><li>item #32</li><li>item #33</li><li>item #34</li><li>item #35</li><li>item #36</li><li>item #37</li><li>item #38</li><li>item #39</li><li>item #40</li><li>item #41</li><li>item #42</li><li>item #43</li><li>item #44</li><li>item #45</li><li>item #46</li><li>item #47</li><li>item #48</li><li>item #49</li><li>item #50</li><li>item #51</li><li>item #52</li><li>item #53</li><li>item #54</li><li>item #55</li><li>item #56</li><li>item #57</li><li>item #58</li><li>item #59</li><li>item #60</li><li>item #61</li><li>item #62</li><li>item #63</li><li>item #64</li><li>item #65</li><li>item #66</li><li>item #67</li><li>item #68</li><li>item #69</li><li>item #70</li><li>item #71</li><li>item #72</li><li>item #73</li><li>item #74</li><li>item #75</li><li>item #76</li><li>item #77</li><li>item #78</li><li>item #79</li><li>item #80</li><li>item #81</li><li>item #82</li><li>item #83</li><li>item #84</li><li>item #85</li><li>item #86</li><li>item #87</li><li>item #88</li><li>item #89</li><li>item #90</li><li>item #91</li><li>item #92</li><li>item #93</li><li>item #94</li><li>item #95</li><li>item #96</li><li>item #97</li><li>item #98</li><li>item #99</li></ul>
<div id="buttons-centerFalse">
<span><code>scrollIntoViewIfNeeded(false)</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<div id="buttons-centerTrue">
<span><code>scrollIntoViewIfNeeded(true)</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<div id="buttons-centerUndefined">
<span><code>scrollIntoViewIfNeeded(undefined)</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<div id="buttons-centerNoArgs">
<span><code>scrollIntoViewIfNeeded()</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<script src="index.js"></script>
<script>
(function () {
var scrollArea = document.getElementById('scroll-area'),
buttonsCenterFalse = document.getElementById('buttons-centerFalse'),
buttonsCenterTrue = document.getElementById('buttons-centerTrue'),
buttonsCenterUndefined = document.getElementById('buttons-centerUndefined'),
buttonsCenterNoArgs = document.getElementById('buttons-centerNoArgs'),
scrollIntoViewIfNeededToItemAndSelect;
scrollIntoViewIfNeededToItemAndSelect = function (itemIdx, centerIfNeeded) {
scrollArea.querySelector('.selected').className = '';
// Allow us to really have difference bewteen scrollIntoViewIfNeeded() and scrollIntoViewIfNeeded(undefined)
if (arguments.length === 1) {
scrollArea.children[itemIdx].scrollIntoViewIfNeeded();
} else {
scrollArea.children[itemIdx].scrollIntoViewIfNeeded(centerIfNeeded);
}
scrollArea.children[itemIdx].className = 'selected';
};
buttonsCenterFalse.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx, false);
}
}, false);
buttonsCenterTrue.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx, true);
}
}, false);
buttonsCenterUndefined.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx, undefined);
}
}, false);
buttonsCenterNoArgs.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx);
}
}, false);
})();
</script>
<!DOCTYPE html>
<title>scrollIntoViewIfNeeded test page</title>
<style type="text/css">
body {
font: 14px Arial;
}
#scroll-area {
border: 1px solid #AAA;
height: 7em;
margin: 0;
overflow: auto;
padding: 0;
}
#scroll-area li {
background: #EEE;
border-radius: 5px;
list-style: none;
padding: 5px 10px;
}
#scroll-area li:nth-child(2n+1) {
background: #DDD;
}
#scroll-area li.selected {
background: #BADA55;
}
</style>
<h1>scrollIntoViewIfNeeded test page</h1>
<ul id="scroll-area"><li class="selected">item #0</li><li>item #1</li><li>item #2</li><li>item #3</li><li>item #4</li><li>item #5</li><li>item #6</li><li>item #7</li><li>item #8</li><li>item #9</li><li>item #10</li><li>item #11</li><li>item #12</li><li>item #13</li><li>item #14</li><li>item #15</li><li>item #16</li><li>item #17</li><li>item #18</li><li>item #19</li><li>item #20</li><li>item #21</li><li>item #22</li><li>item #23</li><li>item #24</li><li>item #25</li><li>item #26</li><li>item #27</li><li>item #28</li><li>item #29</li><li>item #30</li><li>item #31</li><li>item #32</li><li>item #33</li><li>item #34</li><li>item #35</li><li>item #36</li><li>item #37</li><li>item #38</li><li>item #39</li><li>item #40</li><li>item #41</li><li>item #42</li><li>item #43</li><li>item #44</li><li>item #45</li><li>item #46</li><li>item #47</li><li>item #48</li><li>item #49</li><li>item #50</li><li>item #51</li><li>item #52</li><li>item #53</li><li>item #54</li><li>item #55</li><li>item #56</li><li>item #57</li><li>item #58</li><li>item #59</li><li>item #60</li><li>item #61</li><li>item #62</li><li>item #63</li><li>item #64</li><li>item #65</li><li>item #66</li><li>item #67</li><li>item #68</li><li>item #69</li><li>item #70</li><li>item #71</li><li>item #72</li><li>item #73</li><li>item #74</li><li>item #75</li><li>item #76</li><li>item #77</li><li>item #78</li><li>item #79</li><li>item #80</li><li>item #81</li><li>item #82</li><li>item #83</li><li>item #84</li><li>item #85</li><li>item #86</li><li>item #87</li><li>item #88</li><li>item #89</li><li>item #90</li><li>item #91</li><li>item #92</li><li>item #93</li><li>item #94</li><li>item #95</li><li>item #96</li><li>item #97</li><li>item #98</li><li>item #99</li></ul>
<div id="buttons-centerFalse">
<span><code>scrollIntoViewIfNeeded(false)</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<div id="buttons-centerTrue">
<span><code>scrollIntoViewIfNeeded(true)</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<div id="buttons-centerUndefined">
<span><code>scrollIntoViewIfNeeded(undefined)</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<div id="buttons-centerNoArgs">
<span><code>scrollIntoViewIfNeeded()</code> to item : </span>
<button data-item-idx="0">#0</button>
<button data-item-idx="11">#11</button>
<button data-item-idx="22">#22</button>
<button data-item-idx="24">#24</button>
<button data-item-idx="26">#26</button>
<button data-item-idx="33">#33</button>
<button data-item-idx="44">#44</button>
<button data-item-idx="55">#55</button>
<button data-item-idx="66">#66</button>
<button data-item-idx="77">#77</button>
<button data-item-idx="82">#82</button>
<button data-item-idx="84">#84</button>
<button data-item-idx="86">#86</button>
<button data-item-idx="99">#99</button>
</div>
<script src="index.js"></script>
<script>
(function () {
var scrollArea = document.getElementById('scroll-area'),
buttonsCenterFalse = document.getElementById('buttons-centerFalse'),
buttonsCenterTrue = document.getElementById('buttons-centerTrue'),
buttonsCenterUndefined = document.getElementById('buttons-centerUndefined'),
buttonsCenterNoArgs = document.getElementById('buttons-centerNoArgs'),
scrollIntoViewIfNeededToItemAndSelect;
scrollIntoViewIfNeededToItemAndSelect = function (itemIdx, centerIfNeeded) {
scrollArea.querySelector('.selected').className = '';
// Allow us to really have difference bewteen scrollIntoViewIfNeeded() and scrollIntoViewIfNeeded(undefined)
if (arguments.length === 1) {
scrollArea.children[itemIdx].scrollIntoViewIfNeeded();
} else {
scrollArea.children[itemIdx].scrollIntoViewIfNeeded(centerIfNeeded);
}
scrollArea.children[itemIdx].className = 'selected';
};
buttonsCenterFalse.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx, false);
}
}, false);
buttonsCenterTrue.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx, true);
}
}, false);
buttonsCenterUndefined.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx, undefined);
}
}, false);
buttonsCenterNoArgs.addEventListener('click', function (e) {
if (e.target.nodeName === 'BUTTON') {
scrollIntoViewIfNeededToItemAndSelect(e.target.dataset.itemIdx);
}
}, false);
})();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment