Сложного в этом таске я ничего не увидел и приступил к его выполнени. Логика была следующая — при откртии попапа даем body overflow = hidden, а при закрытии естественно его возвращаем в auto. Сам попап кладем в div-обвертку и даем ему overflow = auto, чтобы в случаях, когда размер окна меньше, чем размер попапа — пользователь мог его скроллить.
Под OS X все работало идеально. Но я не учел одного нюанса — в остальных системах в браузере есть постоянно видимый скролл, который имеет свою ширину. И если просто делать блоку, у которого есть скролл, overflow hidden — скролл пропадает, ширина блока становится больше на ширину скролла и контент «прыгает». Тогда я решил написать плагин, который решает и эту проблему. Еще раз посмотрев, как работает сей чудный попап в вконтакте, я понял логику и приступил к выполнению.
(Мне кажется, сайты, которые не используют jQuery — уже редкость. Поэтому писал я плагин с использованием jQuery)
Так как я хотел написать такой плагин, для работы которого не нужно было дописывать какие-либо конструкции в Html и в css, плагин должен это все сделать сам. Для начала стоит разобраться, какой Html-каркас будет нужен плагину. Так как у попапа должен быть фон, котороый будет перекрывать основной контент, в body должен появиться div, который и будет являться непосредственно самим фоном. Это первый элемент. Второй — обертка попапа. Она нужна для того, чтобы во время, когда у body стоит overflow hidden, если наш попап больше по размеру, чем размер окна пользователя — пользователь мог проскролить контейнер с попапом. Для этого у оболочки будет прописано css свойство overflow равное auto. Теперь нужно организвать работу плагина так, чтобы он подставлял эти объекты в наш html.
За работу с попапом будет отвечать объект _JPopup. У него же есть метод init(), который будет «дергаться» после загрузки страницы. В этом методе плагин и будет добавлять нужные для его работы html теги в body.
$(function () { _JPopup.init(); }); var _JPopup = { /** * div который является фоном попапа * jQuery object */ $background: null, /** * Блок, в котором будут находиться все попапы * jQuery object */ $popupBlock: null, $body: null, init: function () { // Наш затухающий фон. Это div, который имеет 100-процентные ширину и высоту this.$background = $('<div style="display: none; position: absolute; z-index: 9997; width: 100%; height: 100%; background-color: #000" id="j_background"></div>'); // оболочка попапа в которую будут вставляться html конструкции this.$popupBlock = $('<div style="display:none;position: fixed; height: 100%; width: 100%; z-index: 9999; overflow: auto;" id="j_popup"></div>'); this.$body = $('body'); this.$body .prepend(this.$background) .prepend(this.$popupBlock); } }
Так же при инициализации попапа стоит подумать и о том, как он будет закрываться. Мне кажется, что большинство пользователей уже привыкло к тому, что при нажатии на контент за пределами попапа сам попап должен закрыться. В нашем случае пользователь не сможет нажать на какой-либо контент за пределами блока $popupBlock, так как у него 100-процентные размеры и самый большой z-index. Получается, что нужно закрывать попап после клика на $popupBlock. Для этого добавляем в метод init() следующий код:
var _JPopup = { ... init: function () { ... this.$popupBlock.click(function (e) { _JPopup.hidePopup(); }); } }
Сам метод _JPopup.hidePopup() мы разберем чуть позже.
Теперь приступим к самому интересному — появлению попапа. В первую очередь нам нужно знать, какой блок будет являться попапом. Есть много вариантов, как реализовать указание плагину на то, какой блок нужно брать. К примеру передавать селектор как параметр, либо же передавать непосредственно сам jQuery объект. Мне же нравится работать с методами jQuery и поэтому хотелось получить возможность вызывать появление попапа подобным способом: $(‘#someId’).showPopup(); Для этого добавим метод jQuery:
$.fn.showPopup = function () { _JPopup.showPopup($(this)); return this; };
Все, что делает данный метод — вызывает метод объекта _JPopup showPopup() и передает ему объект jQuery элемента, к которому была применена функция. Затем, чтобы не нарушать возможность ведения цепочки методов, возвращаем this.
А вот метод _JPopup.showPopup() и будет показывать нужный нам попап.
var _JPopup = { ... showPopup: function ($self) { if($.browser.msie && $.browser.version < 9) this.$background.show(); else this.$background.css('opacity',0).show().fadeTo('slow', 0.7); this.$background.css({'top': this.$body.scrollTop(),'left': this.$body.scrollLeft()}); this.$body.css({'overflow':'hidden','width':$(document).width()-this.getScrollBarWidth()}); var $html = $self.clone(); this.$popupBlock.html('').append($html).show(); $html.css({ 'position': 'absolute', 'z-index': 9999, 'top':'50%', 'left':'50%' }).show(); this.setAlign(); $html.click(function (e) { e.stopPropagation(); }); } }
Разберем этот метод. С самого начала мы показываем полупрозрачный фон. В случае, если пользователь использует IE8 и ниже, фон просто появится, если же юзер использует более цивилизованный браузер — фон появится плавно и будет полупрозрачным за счет css свойства opacity. Плавное появление мы добиваемся с помощью метода fadeTo(‘slow’, 0.7). Если контент страницы будет проскроллен, и так как у нашего фона прописан position: absolute, он не перекроет весь контент. Для того, чтобы такого не произошло, изменим его css значения top и left ровно на столько, на сколько был проскроллен документ.
Затем, мы отключаем скролл у body прописывая ‘overflow’:’hidden’ и в то же время даем ему ширину, равную ширине документа минус ширину скролла. Так как в разных ОС и разных браузерах ширина скролла может быть разная (где-то она вообще равна нулю), мы должны ее (ширину) определить. Для этого я воспользовался кодом, который когда-то где-то нашел. Честно говоря уже даже не помню где. Но он до сих пор верно помогает мне в определении ширины скролла. Учитывая, что ширина скролла не может меняться в одном браузере, для того, чтобы каждый раз не тратить время на ее нахождение, мы создадим переменную, равную null, и только в случае если она равна null будем определять ширину скролла и присваивать это значение этой переменной. В таком случае при повторном обращении мы не будем тратить драгоценное время.
var _JPopup = { ... scrollbarWidth: null, getScrollBarWidth: function () { if ( this.scrollbarWidth === null ) { if ( $.browser.msie && parseInt($.browser.version, 10) === 8) { var $textarea1 = $('<textarea cols="10" rows="2"></textarea>') .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'), $textarea2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>') .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'); this.scrollbarWidth = $textarea1.width() - $textarea2.width(); $textarea1.add($textarea2).remove(); } else { var $div = $('<div />') .css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 }) .prependTo('body').append('<div />').find('div') .css({ width: '100%', height: 200 }); this.scrollbarWidth = 100 - $div.width(); $div.parent().remove(); } } return this.scrollbarWidth; } }
После этого мы получаем Html контента попапа. Для этого воспользуемся ф-цией jQuery clone(). Конечно можно было пойти немного иным способом и вставлять в нашу оболочку непосредственно сам объект, к которому применили метод showPopup, а после закрытия попапа возвращать его на место. Но в таком случае, если в попапе есть, к примеру, инпуты — нужно будет следить за их очисткой. В общем мне показалось что это был бы лишний функционал. Итак. Мы получили html нужного контента для попапа. Теперь вставляем его в оболочку. Причем вставляем мы его заменяя весь Html, который был в оболочке. Это нужно для того, чтобы если вдруг там был другой попап — не вывести заодно и его. Ну и методом show() заменяем display: none оболочки на display: block.
Для того, чтобы контент попапа был «выше» всего остального, ему нужно прописать самый большой z-index. А так же нашей задачей является выравнивание попапа по центру. Для этого дадим ему значения left и top равные 50%. Теперь верхний левый угол попапа находится посередине страницы. А для того, чтобы по центру страницы находился центр попапа нужно дать ему отрицательные margin-left и margin-top равные половине его ширины и высоты соответственно. Вынесем эту логику в отдельный метод setAlign:
var _JPopup = { ... setAlign: function () { var marginLeft = this.$popupBlock.children().width()/ 2, marginTop = this.$popupBlock.children().height()/2; if($(window).width()/2 < marginLeft) marginLeft = $(window).width()/2-10; if($(window).height()/2 < marginTop) marginTop = $(window).height()/2-10; this.$popupBlock.children().css({ 'margin-left': -marginLeft, 'margin-top': -marginTop, 'padding-bottom': '10px' }); }, }
В этом методе мы учитываем и те случаи, когда размер попапа больше размера окна. В этом случае, если задавать margin-left и margin-top равные половине ширины и высоты попапа умноженные на -1 — часть попапа просто скроется и не будет видна пользователю. Поэтому мы проверяем, если половина ширины окна браузера меньше чем половина ширины попапа (лобо если проще — ширина окна меньше ширины попапа) — ‘margin-left’ будет равен половине ширины окна. Плюс делаем отбивочку в 10 пикселей чтобы попап не прилипал к краю окна.
Хотелось бы вернуться к событию клика по попапу, после которого попап закрывается. Если оставить все как есть — попап будет закрываться даже тогда, когда пользователь будет кликать по контенту попапа. Для решения этой проблемы я воспользовался эфектом всплытия событий в jQuery. Как извесно, если у нас span лежит в div и юзер кликнул по span — сначала сработает событие у спана, а затем произойдет событие у дива. Для решения нашей проблемы мы просто предотвращаем всплытие события методом stopPropagation().
Осталось только, как я и обещал, описать метод закрытия попапа. Он будет совсем несложным:
var _JPopup = { ... hidePopup: function () { this.$popupBlock.html('').hide(); this.$background.hide(); this.$body.css('overflow','auto'); this.$body.css('width','auto'); }, }
Метод прячет оболочку попапа с помощью метода hide(). Этим же методом он прячет фон попапа. Затем мы возвращаем возможность скролла у body и даем ему первоначальную ширину. И напишем для него внешнуюю функцию, которую можно вызвать для закрытия попапа «вручну». К примеру по клику на кнопку «Закрыть попап»
function closeJPopup() { _JPopup.hidePopup(); }
Теперь, к примеру, у нас есть блок
<div id="popup" style="display: none;"> <div style="width: 300px;height: 300px; background-color: white;"> some text <input type="text"/> </div> </div>
Все, что нужно для того, чтобы показать его в попапе — вызвать метод $(‘#popup’).showPopup();
Просмотреть работу попапа можно здесь. Там можно проверить как работают попапы как небольших размеров, так и «длинные» попапы — нужно пролистать страницу ниже.
Если кому будет интересно, скачать файл попапа можно по этой ссылке. Там находится более «допиленная» версия плагина. Основное дополнение — возможность передать каллбэк функцию при появлении попапа. А вторым параметром можно передать параметры, которые нужно передать в каллбэк ф-цию. Эта возможность удобна для тех случаев, когда нужно инитить какой-то плагин на контент в попапе. К примеру — JClever.
ссылка на оригинал статьи http://habrahabr.ru/post/218111/
Добавить комментарий