| <!doctype html> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"> |
| <meta name="apple-mobile-web-app-capable" content="yes"> |
| <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> |
| <style> |
| html, body { |
| height: 100%; |
| overflow: hidden; |
| } |
| * { |
| box-sizing: border-box; |
| } |
| .scrollable { |
| position: relative; |
| height: 100%; |
| overflow-y: scroll; |
| overflow-x: hidden; |
| } |
| .stretcher { |
| position: absolute; |
| visibility: hidden; |
| left: 0; |
| top: 0; |
| width: 1px; |
| height: 1000000px; |
| } |
| .item { |
| background-color: #FFF; |
| width: 100%; |
| position: absolute; |
| left: 0; |
| top: 0; |
| -webkit-transform: translate3d(0, 0, 0); |
| height: 50px; |
| border: 1px solid #000; |
| padding: 10px; |
| } |
| body { |
| -ms-touch-action: none; |
| display: none; |
| } |
| @keyframes slideIn { |
| from { |
| transform: translate3d(100%, 0, 0); |
| } |
| to { |
| transform: translate3d(0, 0, 0); |
| } |
| } |
| @keyframes slideOut { |
| from { |
| transform: translate3d(0, 0, 0); |
| } |
| to { |
| transform: translate3d(-100%, 0, 0); |
| } |
| } |
| @-webkit-keyframes slideIn { |
| from { |
| -webkit-transform: translate3d(100%, 0, 0); |
| } |
| to { |
| -webkit-transform: translate3d(0, 0, 0); |
| } |
| } |
| @-webkit-keyframes slideOut { |
| from { |
| -webkit-transform: translate3d(0, 0, 0); |
| } |
| to { |
| -webkit-transform: translate3d(-100%, 0, 0); |
| } |
| } |
| .list { |
| transform: translate3d(100%, 0, 0); |
| -webkit-transform: translate3d(100%, 0, 0); |
| border: 5px solid red; |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| } |
| </style> |
| <script> |
| var LIST_COUNT = 10; |
| for (var i = 0; i < LIST_COUNT; i++) { |
| document.write('<div class="list scrollable" style="border-color: #' + |
| Math.floor(Math.random() * 16777215).toString(16) + '"><div class="stretcher"></div></div>'); |
| } |
| var transform = 'webkitTransform' in document.body.style ? 'webkitTransform' : 'transform'; |
| |
| function InfiniteList(options) { |
| var scrollable = options.element, |
| itemTemplate = options.template, |
| renderFn = options.render, |
| itemHeight = options.itemHeight || 50, |
| itemsPool = [], |
| itemsPoolCount = 0, |
| topItemIndex = 0, |
| scrollableHeight = window.innerHeight, |
| div, i; |
| |
| function update() { |
| var newScrollTop = scrollable.scrollTop, |
| newTopItemIndex = Math.floor(newScrollTop / itemHeight), |
| delta = newTopItemIndex - topItemIndex; |
| |
| if (delta !== 0) { |
| if (Math.abs(delta) > itemsPoolCount) { |
| renderAllItems(newTopItemIndex); |
| } |
| else { |
| renderItems(delta); |
| } |
| } |
| } |
| |
| function renderAllItems(index) { |
| for (i = 0; i < itemsPoolCount; i++) { |
| renderItem(itemsPool[i], index + i); |
| } |
| |
| topItemIndex = index; |
| } |
| |
| function renderItems(count) { |
| var lastIndex = itemsPoolCount - 1; |
| |
| // Recycle top items to render at the bottom |
| if (count > 0) { |
| for (i = 0; i < count; i++) { |
| div = itemsPool[i]; |
| renderItem(div, topItemIndex + itemsPoolCount + i); |
| } |
| |
| itemsPool.push.apply(itemsPool, itemsPool.splice(0, count)); |
| topItemIndex += count; |
| } |
| // Recycle bottom items to render at the top |
| else { |
| count = Math.abs(count); |
| for (i = lastIndex; i > lastIndex - count; i--) { |
| div = itemsPool[i]; |
| renderItem(div, --topItemIndex); |
| } |
| |
| itemsPool.unshift.apply(itemsPool, itemsPool.splice(lastIndex - count + 1, count)); |
| } |
| } |
| |
| function renderItem(div, index) { |
| renderFn(div, index); |
| div.style[transform] = 'translate3d(0, ' + (index * itemHeight) + 'px' + ', 0)'; |
| } |
| |
| function loop() { |
| window.requestAnimationFrame(function() { |
| update(); |
| loop(); |
| }); |
| } |
| |
| window.addEventListener('DOMContentLoaded', function() { |
| console.log('DOMContentLoaded'); |
| var holder = document.createElement('div'), |
| itemsHtml = []; |
| |
| itemsPoolCount = Math.ceil(scrollableHeight / itemHeight) + 1; |
| |
| for (i = 0; i < itemsPoolCount; i++) { |
| itemsHtml.push(itemTemplate); |
| } |
| |
| holder.innerHTML = itemsHtml.join(''); |
| itemsPool.push.apply(itemsPool, holder.children); |
| itemsPool.forEach(function(item) { |
| item.style.height = itemHeight + 'px'; |
| scrollable.appendChild(item); |
| }); |
| |
| renderAllItems(0); |
| loop(); |
| }); |
| } |
| document.body.style.display = 'block'; |
| var lists = document.querySelectorAll('.list'), |
| prefix = 'onwebkitanimationend' in window ? '-webkit-' : '', |
| count = 0, |
| index = 0; |
| [].forEach.call(lists, function(list) { |
| new InfiniteList({ |
| element: list, |
| template: '<div class="item"></div>', |
| render: function(div, index) { |
| div.textContent = 'This is item #' + index; |
| } |
| }); |
| }); |
| function animate(index, name) { |
| var list = lists[index]; |
| list.style.setProperty(prefix + 'animation-name', name, null); |
| list.style.setProperty(prefix + 'animation-duration', '5s', null); |
| list.style.setProperty(prefix + 'animation-timing-function', 'ease-out', null); |
| list.style.setProperty(prefix + 'animation-fill-mode', 'forwards', null); |
| count++; |
| } |
| function next() { |
| if (index > lists.length - 1) { |
| index = 0; |
| } |
| animate(index, 'slideOut'); |
| if (++index > lists.length - 1) { |
| index = 0; |
| } |
| animate(index, 'slideIn'); |
| } |
| document.addEventListener(prefix ? 'webkitAnimationEnd' : 'animationend', function() { |
| console.log('animationend'); |
| if (--count === 0) { |
| next(); |
| } |
| }, true); |
| next(); |
| </script> |