DataLife Engine > Версия для печати > Раздвижная форма поиска

Если вы интересуетесь разработкой веб-сайтов, то наверное заметили небольшие изменения в оформлении известного ресурса Codrops. Изменения коснулись строки поиска, которая теперь располагается сбоку в самом верху сайта. К тому же, при нажатии по иконке поиска строка раздвигается и отображает форму, куда вы можете ввести запрос. Как рассказывают авторы проекта, от посетителей было получено множество вопросов о том, как можно создать такую же форму, и сегодня мы хотим предложить вам подробное руководство, посвященное воссозданию подобного эффекта с нуля.Цель данного руководства заключается в том, чтобы предоставить максимальную совместимость с устаревшими браузерами и мобильными устройствами. Несмотря на то, что эффект кажется невероятно простым, нам придется применить несколько хитрых трюков, чтобы все работало как надо.Давайте составим небольшой план того, что у нас должно получиться:* Сначала нам нужно показать кнопку с иконкой поиска.* При нажатии по иконке, нам нужно чтобы появлялась форма поиска.* Этот компонент должен быть гибким, чтобы мы могли использовать его в адаптивных шаблонах.* Когда мы вписываем что-либо, нам нужно, чтобы подтвердить поиск можно было либо посредством клика по иконке, либо при помощи нажатия клавиши Enter.* Если же форма остается пустой, и мы очередной раз нажимаем по иконке поиска, то форма просто-напросто закрывается.* Также нам нужно, чтобы форма закрывалась, если мы кликаем где-либо на странице за пределами формы поиска.* Нам нужно, чтобы с отключенной поддержкой javascript, форма поиска по умолчанию была раскрыта.* Для более привлекательного опыта взаимодействия на сенсорных устройствах, нам также нужно добавить поддержку touch-событий.Теперь мы знаем, что нужно делать, и давайте приступим к реализации.

Разметка

В разметке нам понадобится основной контейнер, форма, текст и кнопки подтверждения, а также span-элемент для иконки:







Обычно для иконки можно использовать псевдо-элемент, но так как он не предназначен для использования на заменяемых элементах вроде элементов формы, мы в данном случае воспользуемся обычным span-элементом.Когда все элементы будут расположены на своих местах, можно переходить к стилизации.

CSS-стили

Следуя нашим требованиям, сначала нужно убедиться в том, что у нас есть кнопка с видимой иконкой поиска. Остальное должно быть скрыто. Но давайте также зайдем на шаг вперед, и представим, что произойдет, когда мы раскроем форму поиска (нашу основную оболочку). Как мы это реализуем? Давайте воспользуемся overflow: hidden и увеличением ширины оболочки sb-search, которая должна раскрыть нам форму ввода поискового запроса.

Итак, первый элемент, оформлением которого мы занимаемся, это sb-search. Мы выравниваем его по правому краю, и выставляем свойство overflow на hidden. По естеству, ширина должна составлять 60 пикселей, но так как мы хотим анимировать ширину до 100%, у нас могут возникнуть проблемы в мобильных (iOS) браузерах. Они не любят переход с пиксельной меры на процентное соотношение. Следовательно, они просто обойдут стороной этот переход. Вместо этого мы указываем значение 60px в свойство min-width и ширину равную 0%. Это решение было придумано пользователем @julienknebel и вы можете прочесть о CSS-переходе с пиксельного значения на автоматическую ширину здесь.

Мы также добавим переход для ширины и -webkit-backface-visibility: hidden, чтобы избежать кривостей в форме ввода в мобильных (iOS) браузерах:

.sb-search {
    position: relative;
    margin-top: 10px;
    width: 0%;
    min-width: 60px;
    height: 60px;
    float: right;
    overflow: hidden;

    -webkit-transition: width 0.3s;
    -moz-transition: width 0.3s;
    transition: width 0.3s;

    -webkit-backface-visibility: hidden;
}

Все, что выходит за пределы этого маленького блока, будет не видно.Теперь давайте поместим на место форму ввода запроса. Мы выставляем ширину в процентном соотношении, чтобы при расширении родительского элемента, дочерний расширялся вместе с ним. Выставляем нужную высоту, размер шрифта и отступы – это позволит удостовериться в правильном центрировании текста по вертикали (так как использование line-height не сработает даже в IE8).Этап установки формы ввода на абсолютное позиционирование может показаться необязательным, но это решает один неприятный момент, который происходит с каждым закрыванием формы: форма ввода на короткий промежуток времени остается видимой с правой стороны.

.sb-search-input {
    position: absolute;
    top: 0;
    right: 0;
    border: none;
    outline: none;
    background: #fff;
    width: 100%;
    height: 60px;
    margin: 0;
    z-index: 10;
    padding: 20px 65px 20px 20px;
    font-family: inherit;
    font-size: 20px;
    color: #2c3e50;
}

input[type="search"].sb-search-input {
    -webkit-appearance: none;
    -webkit-border-radius: 0px;
}

Вдобавок, мы также удаляем стандартные стили для формы ввода в браузерах семейства WebKit.Давайте укажем цвет текста-заглушки при помощи правил с браузерными префиксами:

.sb-search-input::-webkit-input-placeholder {
    color: #efb480;
}

.sb-search-input:-moz-placeholder {
    color: #efb480;
}

.sb-search-input::-moz-placeholder {
    color: #efb480;
}

.sb-search-input:-ms-input-placeholder {
    color: #efb480;
}

Теперь давайте разберемся с кнопкой с иконкой поиска и кнопкой подтверждения. Нам нужно расположить их в одном и том же месте, так что давайте расположим их в правом углу и зададим одинаковые габариты. Так как они будут лежать поверх друг друга, мы зададим им абсолютное позиционирование:

.sb-icon-search,
.sb-search-submit  {
    width: 60px;
    height: 60px;
    display: block;
    position: absolute;
    right: 0;
    top: 0;
    padding: 0;
    margin: 0;
    line-height: 60px;
    text-align: center;
    cursor: pointer;
}

Изначально нам нужно, чтобы по кнопке с иконкой можно было кликнуть. Затем, когда мы хотим открыть форму ввода запроса, нам нужно, чтобы по форме ввода также можно было кликнуть. Итак, мы выставляем z-index для кнопки подтверждения запроса на -1 в самом начале, и делаем ее прозрачной, и таким образом мы всегда можем видеть span с иконкой поиска:

.sb-search-submit {
    background: #fff; /* IE needs this */
    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /* IE 8 */
    filter: alpha(opacity=0); /* IE 5-7 */
    opacity: 0;
    color: transparent;
    border: none;
    outline: none;
    z-index: -1;
}

Почему бы просто не сделать фон прозрачным? Потому что это вряд ли получится красиво в IE, так как таким образом по элементу нельзя будет кликнуть. Так что, мы используем литой цвет фона, и выставляем уровень непрозрачности на 0.Span с иконкой поиска будет изначально обозначен высоким значением свойства z-index, так как нам нужно, чтобы он был расположен поверх всех остальных элементов. Мы воспользуемся псевдо-элементом :before, чтобы добавить иконку поиска:

.sb-icon-search {
    color: #fff;
    background: #e67e22;
    z-index: 90;
    font-size: 22px;
    font-family: 'icomoon';
    speak: none;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    -webkit-font-smoothing: antialiased;
}

.sb-icon-search:before {
    content: "e000";
}

Также не стоит забывать о том, чтобы в самом начале нашего CSS-кода включить в проект веб-шрифт:

/* Search icon by IcoMoon, made with http://icomoon.io/app/ */
@font-face {
    font-family: 'icomoon';
    src:url('../fonts/icomoon/icomoon.eot');
    src:url('../fonts/icomoon/icomoon.eot?#iefix') format('embedded-opentype'),
        url('../fonts/icomoon/icomoon.woff') format('woff'),
        url('../fonts/icomoon/icomoon.ttf') format('truetype'),
        url('../fonts/icomoon/icomoon.svg#icomoon') format('svg');
    font-weight: normal;
    font-style: normal;
}

Когда все стили будут установлены, мы можем просто выставить ширину оболочки sb-search на 100%, когда мы добавляем класс sb-search-open. Нам нужно, чтобы форма ввода поиска была по умолчанию открыта, если поддержка javascript будет отключена.

.sb-search.sb-search-open,
.no-js .sb-search {
    width: 100%;
}

Давайте изменим цвет span-элемента иконки поиска, и поместим его «под» кнопкой подтверждения, установив в свойство z-index более низкое значение:

.sb-search.sb-search-open .sb-icon-search,
.no-js .sb-search .sb-icon-search {
    background: #da6d0d;
    color: #fff;
    z-index: 11;
}

И наконец, мы выставляем свойство z-index у кнопки подтверждения на более высокое значение, чтобы иметь возможность кликать по ней:

.sb-search.sb-search-open .sb-search-submit,
.no-js .sb-search .sb-search-submit {
    z-index: 90;
}

Теперь все нужные стили определены, давайте переходить к javascript.

javascript-код

Давайте начнем с переключения класса sb-search-open. Мы добавляем класс тогда, когда кликаем по основной оболочке (sb-search), и убираем его, когда кликаем по кнопке подтверждения, но только в том случае, если форма поиска остается пустой. В противном случае, мы подтверждаем форму. Для того, чтобы отключить удаление класса при нажатии по форме ввода (так как триггером, по сути, является вся оболочка), нам нужно предотвратить прослушивание кликов на этом элементе. Это означает, что нажатие по форме ввода не запустит также нажатие по родительским элементам.

;( function( window ) {

    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
                initSearchFn = function( ev ) {
                    if( !classie.has( self.el, 'sb-search-open' ) ) { // open it
                        ev.preventDefault();
                        self.open();
                    }
                    else if( classie.has( self.el, 'sb-search-open' ) && /^s*$/.test( self.inputEl.value ) ) { // close it
                        self.close();
                    }
                }

            this.el.addEventListener( 'click', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
        },
        open : function() {
            classie.add( this.el, 'sb-search-open' );
        },
        close : function() {
            classie.remove( this.el, 'sb-search-open' );
        }
    }

    // add to global namespace
    window.UISearch = UISearch;

} )( window );

Далее нам нужно добавить события для удаления класса sb-search-open при нажатии по иным областям, за пределами строки поиска. Чтобы это заработало, нам также нужно прослушивать события при нажатии по основной оболочке.

;( function( window ) {

    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
                initSearchFn = function( ev ) {
                    ev.stopPropagation();

                    if( !classie.has( self.el, 'sb-search-open' ) ) { // open it
                        ev.preventDefault();
                        self.open();
                    }
                    else if( classie.has( self.el, 'sb-search-open' ) && /^s*$/.test( self.inputEl.value ) ) { // close it
                        self.close();
                    }
                }

            this.el.addEventListener( 'click', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
        },
        open : function() {
            var self = this;
            classie.add( this.el, 'sb-search-open' );
            // close the search input if body is clicked
            var bodyFn = function( ev ) {
                self.close();
                this.removeEventListener( 'click', bodyFn );
            };
            document.addEventListener( 'click', bodyFn );
        },
        close : function() {
            classie.remove( this.el, 'sb-search-open' );
        }
    }

    // add to global namespace
    window.UISearch = UISearch;

} )( window );

Нам также нужно будет позаботиться об обрезке поискового запроса.Также, когда мы кликаем по иконке поиска, нам нужно, чтобы курсор был сфокусирован на форме ввода. Так как это приводит к немного непривлекательному переходу в мобильных браузерах (в то же время открывается клавиатура), нам нужно придумать обходной вариант. Когда мы закрываем строку поиска, мы размываем форму ввода поиска. Это решит некоторые проблемы на некоторых устройствах, которые отображают мигающий курсор даже после того, как форма ввода уже была скрыта.

;( function( window ) {

    // http://stackoverflow.com/a/11381730/989439
    function mobilecheck() {
        var check = false;
        (function(a){if(/(android|ipad|playbook|silk|bbd+|meego).+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
        return check;
    }

    // http://www.jonathantneal.com/blog/polyfills-and-prototypes/
    !String.prototype.trim && (String.prototype.trim = function() {
        return this.replace(/^s+|s+$/g, '');
    });

    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
                initSearchFn = function( ev ) {
                    ev.stopPropagation();
                    // trim its value
                    self.inputEl.value = self.inputEl.value.trim();

                    if( !classie.has( self.el, 'sb-search-open' ) ) { // open it
                        ev.preventDefault();
                        self.open();
                    }
                    else if( classie.has( self.el, 'sb-search-open' ) && /^s*$/.test( self.inputEl.value ) ) { // close it
                        self.close();
                    }
                }

            this.el.addEventListener( 'click', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
        },
        open : function() {
            var self = this;
            classie.add( this.el, 'sb-search-open' );
            // focus the input
            if( !mobilecheck() ) {
                this.inputEl.focus();
            }
            // close the search input if body is clicked
            var bodyFn = function( ev ) {
                self.close();
                this.removeEventListener( 'click', bodyFn );
            };
            document.addEventListener( 'click', bodyFn );
        },
        close : function() {
            this.inputEl.blur();
            classie.remove( this.el, 'sb-search-open' );
        }
    }

    // add to global namespace
    window.UISearch = UISearch;

} )( window );

Чтобы четко работало на мобильных устройствах, нам нужно добавить соответствующие touch-события. Добавление preventDefault в функцию initSearchFn поможет избежать одновременного запуска нажатий и прикосновений на устройствах с поддержкой touch.

;( function( window ) {

    // http://stackoverflow.com/a/11381730/989439
    function mobilecheck() {
        var check = false;
        (function(a){if(/(android|ipad|playbook|silk|bbd+|meego).+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
        return check;
    }

    // http://www.jonathantneal.com/blog/polyfills-and-prototypes/
    !String.prototype.trim && (String.prototype.trim = function() {
        return this.replace(/^s+|s+$/g, '');
    });

    function UISearch( el, options ) {
        this.el = el;
        this.inputEl = el.querySelector( 'form > input.sb-search-input' );
        this._initEvents();
    }

    UISearch.prototype = {
        _initEvents : function() {
            var self = this,
                initSearchFn = function( ev ) {
                    ev.stopPropagation();
                    // trim its value
                    self.inputEl.value = self.inputEl.value.trim();

                    if( !classie.has( self.el, 'sb-search-open' ) ) { // open it
                        ev.preventDefault();
                        self.open();
                    }
                    else if( classie.has( self.el, 'sb-search-open' ) && /^s*$/.test( self.inputEl.value ) ) { // close it
                        ev.preventDefault();
                        self.close();
                    }
                }

            this.el.addEventListener( 'click', initSearchFn );
            this.el.addEventListener( 'touchstart', initSearchFn );
            this.inputEl.addEventListener( 'click', function( ev ) { ev.stopPropagation(); });
            this.inputEl.addEventListener( 'touchstart', function( ev ) { ev.stopPropagation(); } );
        },
        open : function() {
            var self = this;
            classie.add( this.el, 'sb-search-open' );
            // focus the input
            if( !mobilecheck() ) {
                this.inputEl.focus();
            }
            // close the search input if body is clicked
            var bodyFn = function( ev ) {
                self.close();
                this.removeEventListener( 'click', bodyFn );
                this.removeEventListener( 'touchstart', bodyFn );
            };
            document.addEventListener( 'click', bodyFn );
            document.addEventListener( 'touchstart', bodyFn );
        },
        close : function() {
            this.inputEl.blur();
            classie.remove( this.el, 'sb-search-open' );
        }
    }

    // add to global namespace
    window.UISearch = UISearch;

} )( window );

Наконец, для браузеров, которые не поддерживают addEventListener и removeEventListener мы используем polyfill EventListener от Jonathan Neal.

// EventListener | @jon_neal | //github.com/jonathantneal/EventListener
!window.addEventListener && window.Element && (function () {
    function addToPrototype(name, method) {
        Window.prototype[name] = HTMLDocument.prototype[name] = Element.prototype[name] = method;
    }

    var registry = [];

    addToPrototype("addEventListener", function (type, listener) {
        var target = this;

        registry.unshift({
            __listener: function (event) {
                event.currentTarget = target;
                event.pageX = event.clientX + document.documentElement.scrollLeft;
                event.pageY = event.clientY + document.documentElement.scrollTop;
                event.preventDefault = function () { event.returnValue = false };
                event.relatedTarget = event.fromElement || null;
                event.stopPropagation = function () { event.cancelBubble = true };
                event.relatedTarget = event.fromElement || null;
                event.target = event.srcElement || target;
                event.timeStamp = +new Date;

                listener.call(target, event);
            },
            listener: listener,
            target: target,
            type: type
        });

        this.attachEvent("on" + type, registry[0].__listener);
    });

    addToPrototype("removeEventListener", function (type, listener) {
        for (var index = 0, length = registry.length; index < length; ++index) {
            if (registry[index].target == this && registry[index].type == type && registry[index].listener == listener) {
                return this.detachEvent("on" + type, registry.splice(index, 1)[0].__listener);
            }
        }
    });

    addToPrototype("dispatchEvent", function (eventObject) {
        try {
            return this.fireEvent("on" + eventObject.type, eventObject);
        } catch (error) {
            for (var index = 0, length = registry.length; index < length; ++index) {
                if (registry[index].target == this && registry[index].type == eventObject.type) {
                    registry[index].call(this, eventObject);
                }
            }
        }
    });
})();

И на этом всё! Надеемся, что вам понравилось данное руководство, и вы сможете использовать его в будущих проектах!