Скроллинг клавишами и липкие блоки
Решая на чистом js задачу скроллинга клавишами, а также липких блоков из http://javascript.ru/forum/misc/2892...krollinge.html столкнулся с рядом проблем.
Основа: набор таблиц, в каждой из которой набор строк, в первой ячейке каждой строки произвольное содержимое, вторая ячейка - контейнер для липкого блока. Липкие блоки: (см ссылку). Скроллинг: по нажатию символьных клавиш, которые символизируют "вверх" и "вниз" последовательно перемещаться по ячейкам всей совокупности таблиц. Взял за основу метод document.elementFromPoint(x, y), с помощью которого получаю блок и далее работаю с ним (либо перемещаю к нужной ячейке при скроллинге, либо контролирую положение соответствующего липкого блока). По скроллингу: перемещения производятся, но в некоторые моменты document.elementFromPoint(x, y) возвращает не нужный блок, а саму таблицу (даже если не учитывать, что специально прокрутили, чтобы попасть в margin между ячейками, тоже пока не соображу как это обойти), хотя вроде бы чётко смещаю при каждом нажатии клавиши левый верхний угол блока в точку, в которой находился первый блок при загрузке страницы. По липким блокам: задача вроде бы тоже не сложная, но не получается cформировать условия (совладать с координатами, рисование пока не помогает). Может быть у кого есть соображения на эту тему по моей логике решения (необходимый, на мой взгляд, функционал я включил в код), или может быть альтернативные подходы к решению этих задач. <!doctype html> <html> <head> <meta charset="utf-8"> <title>Скроллинг клавишами и липкие блоки</title> </head> <style> .menu { position: fixed; left: 0%; top: 0%; background: maroon; width: 100%; height: 10%; z-index: 10; } #content { position: absolute; left: 0%; top: 10%; } .table { position: relative; background: gray; } .left { width: 500px; background: lightgray; border: 1px solid black; } .right { background: gray; border: 1px solid black; width: 200px; vertical-align: top; } .sticker { position: relative; background: whitesmoke; height: 100px; } </style> <div class="menu"></div> <div id="content"></div> <script> window.onload = function () {//onload begin var content = document.getElementById('content'); //код создания таблиц var inner = ''; var str = ''; for (var i = 0; i < 3; i++) { str += '<table class="table">' for (var j = 0; j < 3; j++) { str += '<tr><td class="left">'; for (var k = 0; k < 31; k++) { inner += 'таблица ' + i + '; строка ' + j + '<br>'; } str += inner + '</td><td class="right"><div class="sticker">sticker' + i + ' - ' + j + '</div></td></tr>'; inner = ''; } str += '</table>' } content.innerHTML = str; //функция получения координат элемента (и не только) относительно документа var getCoords = function (element) {//getCoords begin var elementRect = element.getBoundingClientRect(); var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; var clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0; var clientTop = document.documentElement.clientTop || document.body.clientTop || 0; var left = elementRect.left + scrollLeft - clientLeft; var top = elementRect.top + scrollTop - clientTop; var obj = { left: Math.round(left), top: Math.round(top), scrollTop: Math.round(scrollTop), scrollLeft: Math.round(scrollLeft), clientLeft: Math.round(clientLeft), clientTop: Math.round(clientTop) } return obj; }//getCoords end //функция получения номера по элементу, если элемент не в коллекции -1 var getN = function (element, collection) {//getN begin for (var i = 0; i < length; i++) { if (element == collection[i]) { return i } } return -1; }//getN end //функция получения элемента по номеру, если нет такого элемента -1 var getElement = function (n, collection) {//getElement begin for (var i = 0; i < length; i++) { if (collection[i] == collection[n]) { return collection[i]; } } return -1; }//getElement end //необходимые переменные var leftBlocks = content.getElementsByClassName('left'); //колллекция блоков слева var rightBlocks = content.getElementsByClassName('right');//коллекция блоков справа var stickers = content.getElementsByClassName('sticker');//коллекция липких блоков var length = leftBlocks.length; // = rightBlocks.length; //= stickers.length; var firstLeft = leftBlocks[0]; //первый элемент слева //его координаты - точка отсчёта var upperBound = getCoords(leftBlocks[0]).top;//верхняя граница var leftBound = getCoords(leftBlocks[0]).left; //левая граница var lastLeft = leftBlocks[length - 1]; //последний элемент слева var lastLeftCoords = getCoords(lastLeft); //его координаты //скроллинг клавишами++++++++++++++++++++++++++++++++++++++++++++++++++++++ document.onkeyup = function (e) {//onkeyup begin var e = e || window.event; //для IE //получаем элемент на верхней границе и его параметры var element = document.elementFromPoint(leftBound, upperBound); /**************************************************** вот в этих двух функциях getToUp и getToDown не получается заложить необходиму логику решения***********************************/ var getToUp = function (element) {//getToUp begin var n = getN(element, leftBlocks); var coords = getCoords(element); if ( n != -1 ) {//если элемент действительно тот //if begin //если элемент пересекает верхнюю границу if ( coords.top < coords.scrollTop + upperBound ) { return element; //возвращаем сам элемент } else if ( n != 0 ) { return element = getElement(n - 1, leftBlocks);//возвращаем предыдущий элемент, если он не первый } } else {//если элемент не тот //if else if ( coords.scrollTop > upperBound) { return element = firstLeft;//возвращаем первый элемент } else { return element = lastLeft;//возвращаем последний элемент } }//if end }//getToUp end var getToDown = function (element) {//getToDown begin var n = getN(element, leftBlocks); var coords = getCoords(element); if ( n != -1 ) { //if begin if ( coords.top < coords.scrollTop + upperBound && n != length ) { return element; //возвращаем последующий элемент, если он не последний } else { return element;//возвращаем сам элемент } } else {//if else if ( coords.scrollTop > upperBound) { //return element = ; } else { //return element = ; } }//if end }//getToDown end //проверяем клавиши и получаем нужный блок switch ( e.keyCode ) { case 65: {//нажата клавиша "вверх" (Ф, ф - рус., A, a - eng.) getToUp (element); break; } case 83: {//нажата клавиша "вниз" (В, в - рус., S, s - eng.) getToDown(element); break; } default: return; //вышли, если не та клавиша }//switch end //если дошли, то прокрутили страницу, поместив блок в точку остчёта window.scrollTo(getCoords(element).left - leftBound, getCoords(element).top - upperBound); }//onkeyup end //липкие блоки+++++++++++++++++++++++++++++++++++++++++ window.onscroll = function () {//onscroll begin //получаем элемент var element = document.elementFromPoint(leftBound, upperBound); //var rect = element.getBoundingClientRect(); var n = getN(element, leftBlocks); var elementCoords = getCoords(element);//координаты элемента относительно документа var elementHeight = element.clientHeight;//высота элемента var sticker = getElement(n, stickers);//текущий липкий блок var stickerCoords = getCoords(sticker);//координаты липкого блока относительно документа var stickerHeight = sticker.clientHeight;//высота липкого блока var scroll = elementCoords.scrollTop;//= stickerCoords.scrollTop //величина прокрутки var scrollAndUpperBound = scroll + upperBound; //величина прокуртки с учётом верхней границы var displacement = scrollAndUpperBound - elementCoords.top; //величина смещения //выравнивание по верхней границе if ( stickerCoords.top < scrollAndUpperBound || stickerCoords.top > scrollAndUpperBound) { sticker.style.top = displacement + 'px'; } //получение новых координат stickerCoords = getCoords(sticker); scrollAndUpperBound = stickerCoords.scrollTop + upperBound; elementCoords = getCoords(element); displacement = scrollAndUpperBound - elementCoords.top; //выравнивание по нижней границе if ( stickerCoords.top + stickerHeight >= elementCoords.top + elementHeight ) { sticker.style.top = elementHeight - stickerHeight + 'px'; } else if (stickerCoords.top > scrollAndUpperBound ) { sticker.style.top = displacement + 'px'; } if (element != lastLeft) { var nextSticker = getElement(n + 1, stickers); nextSticker.style.top = '0 px'; } if ( element == firstLeft && displacement == 0 ) { sticker.style.top = '0 px'; } }//onscroll end }//onload end </script> </body> </html> |
Цитата:
Что надо в целом вообще не понял. Правильно сформулированная задача уже часть решения |
nerv_, посмотрите подредактированный пример, в принципе, хотя пока и коряво, удалось частично решить вторую часть задачи - блоки начали двигаться примерно так как надо, то есть липнуть при прокрутке к нижней части строки меню (тут можно и с position: relative, главное правильно рассчитать координаты, что собственно и не получается).
Насчёт скроллинга клавишами - когда нажимаете клавишу "вверх" должно прокручиваться на предыдущую строку от той, которая в данный момент сразу под строкой меню, "вниз" - на следующую строку, для прокрутки здесь используются клавиши A - вверх, S - вниз. |
Липкие блоки более-менее работают (в FF наиболее плавно, в хроме и IE видны подёргивания при прокрутке), но остаётся проблема попадания на нужный блок при помощи document.elementFromPoint(x, y).
Кто-нибудь знает как отследить ближайший блок, если этот метод выдал в качестве элемента, например, не ячейку таблицы, а саму таблицу (например, когда попал в margin между ячейками)? Специально стал использовал document.elementFromPoint(x, y), чтобы обойтись без цикла, но при таком раскладе проще будет всё рассчитать, если за основу поиска взять именно цикл, а не этот метод. Может быть можно как-нибудь обойтись без цикла? |
bes, добрый день :)
obj = { left: Math.round(left), top: Math.round(top), scrollTop: Math.round(scrollTop), scrollLeft: Math.round(scrollLeft), clientLeft: Math.round(clientLeft), clientTop: Math.round(clientTop) } obj - глобальная переменная getN = function (element, collection) {//getN begin getN - глобальная getElement = function (n, collection) {//getElement begin getElement - глобальная getCoords = function (element) {//getCoords begin getCoords - глобальная Дальше продолжать? ) Цитата:
|
nerv_, спасибо, подправил, попробую и этот способ.
В IE8 не работает из-за getElementsByClassName(). |
bes,
:yes: Мож стоит типо: 1. Выяснем, какие блоки находяться в зоне видимости, 2. Тестируем расстояние нижнего края верхнего видимого блока(посколь их может быть и два) от верха экрана, ежели оно менее его высоты, даем липкому блоку спецфическое id и позицию fixed , ежели оно менее высоты липкого блока, вычислем его координаты и закрепляем позицией absolute убираем id id - удобно для ie |
Deff, я взял за основу несколько другой способ, его уж и закончу))
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Скроллинг клавишами и липкие блоки</title> </head> <style> .menu { position: fixed; left: 0%; top: 0%; background: maroon; width: 100%; height: 10%; z-index: 10; } #content { position: absolute; left: 0%; top: 10%; } .table { position: relative; background: gray; } .left { width: 500px; background: lightgray; border: 1px solid black; } .right { background: gray; border: 1px solid black; width: 200px; vertical-align: top; } .sticker { position: relative; background: whitesmoke; height: 100px; } </style> <div class="menu"></div> <div id="content"></div> <script> window.onload = function () {//onload begin var content = document.getElementById('content'); //код создания таблиц var inner = ''; var str = ''; for (var i = 0; i < 3; i++) { str += '<table class="table">' for (var j = 0; j < 3; j++) { str += '<tr><td class="left">'; for (var k = 0; k < 31; k++) { inner += 'таблица ' + i + '; строка ' + j + '<br>'; } str += inner + '</td><td class="right"><div class="sticker">sticker ' + i + ' - ' + j + '</div></td></tr>'; inner = ''; } str += '</table>'; } content.innerHTML = str; //функция получения координат элемента относительно документа var getCoords = function (element) {//getCoords begin var elementRect = element.getBoundingClientRect(); var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; var clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0; var clientTop = document.documentElement.clientTop || document.body.clientTop || 0; var left = elementRect.left + scrollLeft - clientLeft; var top = elementRect.top + scrollTop - clientTop; var bottom = elementRect.bottom + scrollTop - clientTop; var obj = { left: Math.round(left), top: Math.round(top), bottom: Math.round(bottom), } return obj; }//getCoords end //функция получения координат всех элементов var getAllCoords = function (collection) {//getAllCoords begin var allCoords = []; for (var i = 0; i < length; i++) { allCoords[i] = getCoords(collection[i]); } return allCoords; }//getAllCoords end if (!content.getElementsByClassName) { //if begin content.getElementsByClassName = function(cl) {//getElementsByClassName begin var retnode = []; var myclass = new RegExp('\\b'+cl+'\\b'); var elem = this.getElementsByTagName('*'); for (var i = 0; i < elem.length; i++) { var classes = elem[i].className; if (myclass.test(classes)) { retnode.push(elem[i]); } } return retnode; }//if end }//getElementsByClassName end //необходимые переменные var leftBlocks = content.getElementsByClassName('left'); //колллекция блоков слева var rightBlocks = content.getElementsByClassName('right');//коллекция блоков справа var stickers = content.getElementsByClassName('sticker');//коллекция липких блоков var length = leftBlocks.length; // = rightBlocks.length; //= stickers.length; //получаем координаты всех элементов слева var allCoords = getAllCoords(leftBlocks); var upperBound = allCoords[0].top;//верхняя граница //пересчитываем координаты при масштабировании страницы window.onresize = function () { allCoords = getAllCoords(leftBlocks); upperBound = allCoords[0].top; } //скроллинг клавишами++++++++++++++++++++++++++++++++++++++++++++++++++++++ document.onkeyup = function (e) {//onkeyup begin var e = e || window.event; //для IE //получаем прокрутку var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; //функция получения номера элемента на верхней границе для движения вверх var getNUp = function(coords, boundary) {//getNUp begin //расстояние от начала документа до верхней границы var sum = scrollTop + boundary; for (var i = 0; i < length; i++) {//for begin //если элемент ниже верхней границы и не первый, возвращаем номер предыдущего элемента if (coords[i].top >= sum && i != 0) { return (i - 1); } else //иначе, если элемент пересекает верхнюю границу, возвращаем его номер if (coords[i].bottom > sum) { return i; } }//for end //если дошли, то возвращаем номер самого элемента (он будет последним при полной прокрутке) return (i - 1); }//getNUp end //функция получения номера элемента на верхней границе для движения вниз var getNDown = function (coords, boundary) {//getNDown begin //расстояние от начала документа до верхней границы var sum = scrollTop + boundary; for (var i = 0; i < length; i++) {//for begin //если элемент пересекает верхнюю границу, возвращаем номер последующего элемента, если он не последний if (coords[i].bottom > sum && i != length - 1) { return (i + 1); } else //иначе, если элемент ниже верхней границы, возвращаем его номер if (coords[i].top > sum) { return i; //(i - 1), так как i в цикле увеличивается при выходе } }//for end //если дошли, то возвращаем номер самого элемента (он будет последним при полной прокрутке) return (i - 1);// }//getNDown end //проверяем клавиши и получаем номер нужного блока var n = 0; switch ( e.keyCode ) { case 65: {//нажата клавиша "вверх" (Ф, ф - рус., A, a - eng.) var n = getNUp(allCoords, upperBound); break; } case 83: {//нажата клавиша "вниз" (В, в - рус., S, s - eng.) var n = getNDown(allCoords, upperBound); break; } default: return; //вышли, если не та клавиша }//switch end //если дошли, то прокрутили страницу, поместив начало блока на верхнюю границу window.scrollTo(0, allCoords[n].top - upperBound); }//onkeyup end //липкие блоки+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ window.onscroll = function () {//onscroll begin var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; var sum = scrollTop + upperBound; var stickerCoords = {}; //получаем липкий блок и задаём ему нужные координаты относительно родительского контейнера for (var i = 0; i < length; i++) {//for begin stickerCoords = getCoords(stickers[i]); //если липкий блок в границах своего контейнера, то выровнять липкий блок по верхней границе if (stickerCoords.top >= allCoords[i].top && stickerCoords.bottom <= allCoords[i].bottom ) { stickers[i].style.top = sum - allCoords[i].top + 'px'; //return; } //корректирование предыдущего действия stickerCoords = getCoords(stickers[i]); //если липкий блок зашёл за верхнюю границу своего контейнера, то выровнять по верхнему краю своего контейнера if (stickerCoords.top < allCoords[i].top) { stickers[i].style.top = 0 + 'px'; } //если контейнер липкого блока ниже верхней границы, то выровнять его липкий блок по его верхнему краю if (allCoords[i].top > sum) { stickers[i].style.top = 0 + 'px'; return; } /* if (window.getComputedStyle) { var stickerMarginBottom = parseInt(getComputedStyle(stickers[i], null).marginBottom); } else { var stickerMarginBottom = stickers[i].currentStyle.marginBottom; } */ //если липкий блок зашёл за нижнюю границу своего контейнера, то выровнять по нижнему краю своего контейнера if (stickerCoords.bottom > allCoords[i].bottom) { stickers[i].style.top = (allCoords[i].bottom - allCoords[i].top) - (stickerCoords.bottom - stickerCoords.top) - 2 + 'px'; } }//for end }//onscroll end }//onload end </script> </body> </html> |
Подредактировал предыдущий пример, теперь рабочий (в IE<9 не будет работать из-за getElementsByClassName и getComputedStyle, тут нужно эмулировать, остальное вроде бы кроссбраузерно).
Лучше сформулировать логику установки координат для липких блоков пока не могу, тут если умные люди подключатся, подскажут, но, главное, работает. nerv_, Deff, спасибо за помощь и участие. |
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Скроллинг клавишами и липкие блоки</title> </head> <style> .menu { position: fixed; left: 0%; top: 0%; background: maroon; width: 100%; height: 10%; z-index: 10; } #content { position: absolute; left: 0%; top: 10%; } .table { position: relative; background: gray; } .block { position: relative; width: 500px; background: lightgray; border: 1px solid black; } .stickerBlock { background: gray; border: 1px solid black; width: 200px; vertical-align: top; } .sticker { position: relative; background: whitesmoke; height: 100px; width: 200px; } </style> <div class="menu"></div> <div id="content"></div> <script> window.onload = function () {//onload begin var content = document.getElementById('content'); var inner = ''; var str = ''; for (var i = 0; i < 3; i++) { str += '<table class="table">' for (var j = 0; j < 3; j++) { str += '<tr><td class="block">'; for (var k = 0; k < 31; k++) { inner += 'таблица ' + i + '; строка ' + j + '<br>'; } str += inner + '</td><td class="stickerBlock"><div class="sticker">sticker ' + i + ' - ' + j + '</div></td></tr>'; inner = ''; } str += '</table>'; } content.innerHTML = str; var getCoords = function (element) {//getCoords begin var rect = element.getBoundingClientRect(); var docEl = document.documentElement; var body = document.body; var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; var clientLeft = docEl.clientLeft || body.clientLeft || 0; var clientTop = docEl.clientTop || body.clientTop || 0; var left = rect.left + scrollLeft - clientLeft; var top = rect.top + scrollTop - clientTop; var right = rect.right + scrollLeft - clientLeft; var bottom = rect.bottom + scrollTop - clientTop; return {left: left, top: top, bottom: bottom, right: right} }//getCoords end if (!content.getElementsByClassName) { //if begin content.getElementsByClassName = function(cl) {//getElementsByClassName begin var retnode = []; var myclass = new RegExp('\\b'+cl+'\\b'); var elem = this.getElementsByTagName('*'); for (var i = 0; i < elem.length; i++) { var classes = elem[i].className; if (myclass.test(classes)) { retnode.push(elem[i]); } } return retnode; }//getElementsByClassName end }//if end /* if (!content.getElementsByClassName) { //if begin content.getElementsByClassName = function(nameOfClass) {//getElementsByClassName begin var mas = []; var elements = this.getElementsByTagName('*'); var length = elements.length; for (var i = 0; i < length; i++) { if (elements[i].className == nameOfClass) { mas.push(elements[i]); } } return mas; }//getElementsByClassName end }//if end */ //скроллинг клавишами++++++++++++++++++++++++++++++++++++++++++++++++++++++ document.onkeyup = function (e) {//onkeyup begin var e = e || window.event; //для IE var getNUp = function(collection, boundary) {//getNUp begin var scroll = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; var sum = scroll + boundary; var length = collection.length; var coords = {}; for (var i = 0; i < length; i++) {//for begin coords = getCoords(collection[i]); if (coords.top >= sum && i != 0) { return (i - 1); } else if (coords.bottom > sum) { return i; } }//for end return (i - 1); }//getNUp end var getNDown = function (collection, boundary) {//getNDown begin var scroll = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; var sum = scroll + boundary; var length = collection.length; var coords = {}; for (var i = 0; i < length; i++) {//for begin coords = getCoords(collection[i]); if (i == 0 && coords.top > sum + 1) {return i} if (coords.bottom > sum && i != length - 1) { return (i + 1); } else if (coords.top > sum) { return i; } }//for end return (i - 1); }//getNDown end var blocks = content.getElementsByClassName('block'); var upperBound = getCoords(blocks[0]).top; var leftBound = getCoords(blocks[0]).left; var n = 0; switch ( e.keyCode ) {//switch begin case 65: { var n = getNUp(blocks, upperBound); break; } case 83: { var n = getNDown(blocks, upperBound); break; } default: return; }//switch begin window.scrollTo(getCoords(blocks[n]).left - leftBound, getCoords(blocks[n]).top - upperBound); }//onkeyup end //липкие блоки+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ window.onscroll = function () {//onscroll begin var stickerBlocks = content.getElementsByClassName('stickerBlock'); var stickers = content.getElementsByClassName('sticker'); var length = stickers.length; var upperBound = getCoords(stickerBlocks[0]).top; var leftBound = getCoords(stickerBlocks[0]).left; var scroll = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; var sum = scroll + upperBound; var coords = {}; var stickerCoords = {}; var condition; for (var i = 0; i < length; i++) {//for begin coords = getCoords(stickerBlocks[i]); stickerCoords = getCoords(stickers[i]); condition = parseInt(stickers[i].style.top) != parseInt(coords.bottom - coords.top - (stickerCoords.bottom - stickerCoords.top) - 1); if (coords.bottom < sum) { continue; } if (coords.top < sum && stickers[i].style.position != 'fixed' && condition) { stickers[i].style.position = 'fixed'; stickers[i].style.top = upperBound + 'px'; } coords = getCoords(stickerBlocks[i]); stickerCoords = getCoords(stickers[i]); if (stickerCoords.bottom > coords.bottom) { stickers[i].style.position = 'relative'; stickers[i].style.top = parseInt(coords.bottom - coords.top - (stickerCoords.bottom - stickerCoords.top) - 1) + 'px'; } coords = getCoords(stickerBlocks[i]); stickerCoords = getCoords(stickers[i]); if (stickerCoords.top < coords.top && i != 0) { stickers[i].style.position = 'relative'; stickers[i].style.top = 0 + 'px'; } condition = parseInt(stickers[i].style.top) == parseInt(coords.bottom - coords.top - (stickerCoords.bottom - stickerCoords.top) - 1); coords = getCoords(stickerBlocks[i]); stickerCoords = getCoords(stickers[i]); if (stickerCoords.top > sum && condition) { stickers[i].style.position = 'fixed'; stickers[i].style.top = upperBound + 'px'; break; } }//for end }//onscroll end }//onload end </script> </body> </html> |
Часовой пояс GMT +3, время: 06:53. |