Skip to content

Instantly share code, notes, and snippets.

@borm
Forked from jocki84/README.md
Last active July 22, 2016 08:50
Show Gist options
  • Save borm/6b42a9054aef57c50cdd8abe540cf76b to your computer and use it in GitHub Desktop.
Save borm/6b42a9054aef57c50cdd8abe540cf76b to your computer and use it in GitHub Desktop.
scrollIntoViewIfNeeded 4 everyone!!!

Polyfill for scrollIntoViewIfNeeded()

This gist provides polyfill code for the scrollIntoViewIfNeeded() Element method found on WebKit browsers.

Features

There is no particular requirement on the position in the hierarchy of the element being made visible with respect to any scrollable areas. Elements that are relatively positioned as well as nested scrollable areas are both supported.

Licence

This code is distributed under CC-BY.

origin

if (!Element.prototype.scrollIntoViewIfNeeded) {
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
function withinBounds(value, min, max, extent) {
if (false === centerIfNeeded || max <= value + extent && value <= min + extent) {
return Math.min(max, Math.max(min, value));
} else {
return (min + max) / 2;
}
}
function makeArea(left, top, width, height) {
return { "left": left, "top": top, "width": width, "height": height
, "right": left + width, "bottom": top + height
, "translate":
function (x, y) {
return makeArea(x + left, y + top, width, height);
}
, "relativeFromTo":
function (lhs, rhs) {
var newLeft = left, newTop = top;
lhs = lhs.offsetParent;
rhs = rhs.offsetParent;
if (lhs === rhs) {
return area;
}
for (; lhs; lhs = lhs.offsetParent) {
newLeft += lhs.offsetLeft + lhs.clientLeft;
newTop += lhs.offsetTop + lhs.clientTop;
}
for (; rhs; rhs = rhs.offsetParent) {
newLeft -= rhs.offsetLeft + rhs.clientLeft;
newTop -= rhs.offsetTop + rhs.clientTop;
}
return makeArea(newLeft, newTop, width, height);
}
};
}
var parent, elem = this, area = makeArea(
this.offsetLeft, this.offsetTop,
this.offsetWidth, this.offsetHeight);
while ((parent = elem.parentNode) instanceof HTMLElement) {
var clientLeft = parent.offsetLeft + parent.clientLeft;
var clientTop = parent.offsetTop + parent.clientTop;
// Make area relative to parent's client area.
area = area.
relativeFromTo(elem, parent).
translate(-clientLeft, -clientTop);
parent.scrollLeft = withinBounds(
parent.scrollLeft,
area.right - parent.clientWidth, area.left,
parent.clientWidth);
parent.scrollTop = withinBounds(
parent.scrollTop,
area.bottom - parent.clientHeight, area.top,
parent.clientHeight);
// Determine actual scroll amount by reading back scroll properties.
area = area.translate(clientLeft - parent.scrollLeft,
clientTop - parent.scrollTop);
elem = parent;
}
};
}
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
div.block {
display: inline-block;
width: 2em;
height: 2em;
border: thick solid blue;
}
div.outer-window {
/*position: relative;*/
width: 9.5em;
height: 9.5em;
overflow: scroll;
}
div.window {
display: inline-block;
/*position: relative;*/
width: 7.5em;
height: 7.5em;
overflow: scroll;
border: thick solid black;
}
div.line {
width: 30em;
border: thick solid green;
}
table {
display: inline-block;
}
</style>
<script src="scrollIntoViewIfNeeded.js"></script>
<script type="text/javascript">
document.documentElement.addEventListener("click", function (evt) {
var elem, target = evt.target;
while (target && "TD" !== target.nodeName) {
target = target.parentNode;
}
if (target) {
elem = document.getElementById(target.textContent);
if (elem) {
elem.scrollIntoViewIfNeeded();
}
}
});
</script>
</head>
<body>
<div>
<table>
<tr><td>00</td><td>01</td><td>02</td></tr>
<tr><td>03</td><td>04</td><td>05</td></tr>
<tr><td>06</td><td>07</td><td>08</td></tr>
</table>
<table>
<tr><td>10</td><td>11</td><td>12</td></tr>
<tr><td>13</td><td>14</td><td>15</td></tr>
<tr><td>16</td><td>17</td><td>18</td></tr>
</table>
<table>
<tr><td>20</td><td>21</td><td>22</td></tr>
<tr><td>23</td><td>24</td><td>25</td></tr>
<tr><td>26</td><td>27</td><td>28</td></tr>
</table>
</div>
<div>
<table>
<tr><td>30</td><td>31</td><td>32</td></tr>
<tr><td>33</td><td>34</td><td>35</td></tr>
<tr><td>36</td><td>37</td><td>38</td></tr>
</table>
<table>
<tr><td>40</td><td>41</td><td>42</td></tr>
<tr><td>43</td><td>44</td><td>45</td></tr>
<tr><td>46</td><td>47</td><td>48</td></tr>
</table>
<table>
<tr><td>50</td><td>51</td><td>52</td></tr>
<tr><td>53</td><td>54</td><td>55</td></tr>
<tr><td>56</td><td>57</td><td>58</td></tr>
</table>
</div>
<div>
<table>
<tr><td>60</td><td>61</td><td>62</td></tr>
<tr><td>63</td><td>64</td><td>65</td></tr>
<tr><td>66</td><td>67</td><td>68</td></tr>
</table>
<table>
<tr><td>70</td><td>71</td><td>72</td></tr>
<tr><td>73</td><td>74</td><td>75</td></tr>
<tr><td>76</td><td>77</td><td>78</td></tr>
</table>
<table>
<tr><td>80</td><td>81</td><td>82</td></tr>
<tr><td>83</td><td>84</td><td>85</td></tr>
<tr><td>86</td><td>87</td><td>88</td></tr>
</table>
</div>
<p>
The items above are arranged in nested scroll containers below.
Click an item in the table above and watch it scrollIntoViewIfNeeded below.
</p>
<div class="outer-window"
><div class="line"
><div class="window"
><div class="line"
><div id="00" class="block">00</div
><div id="01" class="block">01</div
><div id="02" class="block">02</div
></div><div class="line"
><div id="03" class="block">03</div
><div id="04" class="block">04</div
><div id="05" class="block">05</div
></div><div class="line"
><div id="06" class="block">06</div
><div id="07" class="block">07</div
><div id="08" class="block">08</div
></div
></div
><div class="window"
><div class="line"
><div id="10" class="block">10</div
><div id="11" class="block">11</div
><div id="12" class="block">12</div
></div><div class="line"
><div id="13" class="block">13</div
><div id="14" class="block">14</div
><div id="15" class="block">15</div
></div><div class="line"
><div id="16" class="block">16</div
><div id="17" class="block">17</div
><div id="18" class="block">18</div
></div
></div
><div class="window"
><div class="line"
><div id="20" class="block">20</div
><div id="21" class="block">21</div
><div id="22" class="block">22</div
></div><div class="line"
><div id="23" class="block">23</div
><div id="24" class="block">24</div
><div id="25" class="block">25</div
></div><div class="line"
><div id="26" class="block">26</div
><div id="27" class="block">27</div
><div id="28" class="block">28</div
></div
></div
></div
><div class="line"
><div class="window"
><div class="line"
><div id="30" class="block">30</div
><div id="31" class="block">31</div
><div id="32" class="block">32</div
></div><div class="line"
><div id="33" class="block">33</div
><div id="34" class="block">34</div
><div id="35" class="block">35</div
></div><div class="line"
><div id="36" class="block">36</div
><div id="37" class="block">37</div
><div id="38" class="block">38</div
></div
></div
><div class="window"
><div class="line"
><div id="40" class="block">40</div
><div id="41" class="block">41</div
><div id="42" class="block">42</div
></div><div class="line"
><div id="43" class="block">43</div
><div id="44" class="block">44</div
><div id="45" class="block">45</div
></div><div class="line"
><div id="46" class="block">46</div
><div id="47" class="block">47</div
><div id="48" class="block">48</div
></div
></div
><div class="window"
><div class="line"
><div id="50" class="block">50</div
><div id="51" class="block">51</div
><div id="52" class="block">52</div
></div><div class="line"
><div id="53" class="block">53</div
><div id="54" class="block">54</div
><div id="55" class="block">55</div
></div><div class="line"
><div id="56" class="block">56</div
><div id="57" class="block">57</div
><div id="58" class="block">58</div
></div
></div
></div
><div class="line"
><div class="window"
><div class="line"
><div id="60" class="block">60</div
><div id="61" class="block">61</div
><div id="62" class="block">62</div
></div><div class="line"
><div id="63" class="block">63</div
><div id="64" class="block">64</div
><div id="65" class="block">65</div
></div><div class="line"
><div id="66" class="block">66</div
><div id="67" class="block">67</div
><div id="68" class="block">68</div
></div
></div
><div class="window"
><div class="line"
><div id="70" class="block">70</div
><div id="71" class="block">71</div
><div id="72" class="block">72</div
></div><div class="line"
><div id="73" class="block">73</div
><div id="74" class="block">74</div
><div id="75" class="block">75</div
></div><div class="line"
><div id="76" class="block">76</div
><div id="77" class="block">77</div
><div id="78" class="block">78</div
></div
></div
><div class="window"
><div class="line"
><div id="80" class="block">80</div
><div id="81" class="block">81</div
><div id="82" class="block">82</div
></div><div class="line"
><div id="83" class="block">83</div
><div id="84" class="block">84</div
><div id="85" class="block">85</div
></div><div class="line"
><div id="86" class="block">86</div
><div id="87" class="block">87</div
><div id="88" class="block">88</div
></div
></div
></div
></div>
</body>
</html>
<!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="scrollIntoViewIfNeeded.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="scrollIntoViewIfNeeded.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