레이어팝업 플러그인 제작기

작성자 : 신용준

어느 평화로운 평일, 아침회의에 나온 팀장님 의견하나.
“레이어팝업 닫을때 확인창이 떴으면 좋겠네” 를 시작으로 너도나도 레이어팝업에 추가되었으면 좋겠는 기능들을 내놓기 시작하였다.

회의록을 작성하면서 이런 모든 기능이 있는 레이어팝업 플러그인을 찾을수있을까? 라는 생각이 들었다가 “그냥 나더러 새로 하나 만들라는 거구나” 라는 결론에 도달하였고, 오늘은 그 제작 과정을 포스팅 해보겠다.



1. 기능정리

레이어팝업 기본기능에 회의때 나왔던 기능들을 추가하여 정리한 내용들이다.

  1. 레이어팝업 오픈 모션
  2. 내부 마크업 팝업과 외부문서 팝업 지원 (ajax)
  3. open, close시점에 각각 callback함수 내장
  4. baclground클릭 시 팝업닫기 기능 (option으로 선택할 수 있게)
  5. input을 포함하는 팝업일 경우 confirm 기능 추가
  6. 기본적으로 가운데정렬이고 팝업창이 window height보다 길어졌을때 독립스크롤
  7. 외부요소에 의해 제어가능하도록 methods 만들기
  8. iframe내부에서 실행할 경우 부모 document에 접근할 수 있는 유연성



2. 제작과정

우선 즉시실행함수로 closer block을 만든 후 namespace에 prototype을 사용해서 기능들을 하나씩 추가해나가기로 했다.

;(function ($) {
    var Sunrise = (function () {
        var fnidx = 0;

        function Sunrise (settings) {
            var _ = this;

            _.init();
        }

        return Sunrise;
    })();

    Sunrise.prototype.init = function () {
        var _ = this;

        // 기능 추가
    }

    window.sunrise = function () {
        return new Sunrise(arguments[0]);
    }
})(jQuery);



기본옵션에 사용자옵션을 덮을수 있는 구문 추가 + 변수 선언

_.defaults = {
    target: null, // 팝업
    closeConfirm: null, // confirm 기능을 사용할때 검증이필요한 input 배열
    closeConfirmText: 'Are you sure?', // confirm 창 텍스트
    backgroundClose: true, // background 클릭 시 팝업을 닫을지 여부
    ajax: false, // ajax팝업 여부
    dataType: 'html', // ajax팝업일 경우 문서 타입

    openCallback: function () {}, // open 콜백 함수
    closeCallback: function () {} // close 콜백 함수
}

_.options = $.extend(true, _.defaults, settings);

_.initial = {
    fnidx: ++fnidx, // 플러그인 유니크 인덱스 값 부여
    saveDefaultValue: [], // close confirm 기능 사용 시 input 기본 value 저장
    isClose: true // close 기능에 필요한 값
}

namespace.options에는 옵션을 담고 그 외 변수들은 namespace.initial에 담기로 규칙을 정했다.



마크업 생성

_.markups = {
    outer: '<div class="sunrise-outer">',
    scrll: '<div class="sunrise-scrll">',
    inner: '<div class="sunrise-inner">',
    scene: '<div class="sunrise-scene">',
    frame: '<div class="sunrise-frame">'
}


_.element = {
    body: $('body'),
    target: !_.options.ajax ? $(_.options.target) : null
}

_.events = {
    click: 'click.sunrise'+_.initial.fnidx
}

...


Sunrise.prototype.layoutMarkup = function () {
    var _ = this;

    _.initial.scrollTop = $(window).scrollTop();
    _.element.body.addClass('sunrise-fixed');
    _.element.outer = _.element.body.append(_.markups.outer).children('.sunrise-outer:last-child');
    _.element.scrll = _.element.outer.append(_.markups.scrll).children('.sunrise-scrll');
    _.element.inner = _.element.scrll.append(_.markups.inner).children('.sunrise-inner');
    _.element.scene = _.element.inner.append(_.markups.scene).children('.sunrise-scene');
    _.element.frame = _.element.scene.append(_.markups.frame).children('.sunrise-frame');
}

이벤트 충돌을 막기위해 이벤트에 유니크네임을 부여해주고,
가운데 정렬 및 팝업이 길어졌을때를 대비해서 5개의 팝업틀을 생성했다.
개인적으로 코드결벽이있어서 class이름의 글자수를 맞추는것을 좋아한다.




CSS작업
크로니움 기반 최신 브라우저에서는 flex로 가운데 정렬이 되도록 구현했고, 미운손가락같은 IE도 table cell을 사용하여 호환시켰다.

@charset "utf-8";

body {position: relative; -webkit-overflow-scrolling: touch;}
body.sunrise-fixed {overflow: hidden;}

.sunrise-outer {position: fixed; top: -1px; left: -1px; width: calc(100vw + 2px); height: calc(100vh + 2px); z-index: 9999; background-color: rgba(0, 0, 0, 0.5); transform: translateZ(0);}
.sunrise-scrll {overflow: auto; position: relative; width: 100vw; height: 100vh;}
.sunrise-inner {display: flex; flex-direction: row; position: relative; min-width: 100%; min-height: 100%; cursor: url(images/close_23x23_white.png), pointer;}
.sunrise-scene {position: relative; margin: auto; padding: 50px; cursor: url(images/close_23x23_white.png), pointer;}
.sunrise-frame {overflow: hidden; position: relative; padding: 30px; border-radius: 5px; background: #ffffff; opacity: 0; transform: translateY(50px); cursor: default; transition: opacity 500ms, transform 500ms;}
.sunrise-outer.sunrise-visible .sunrise-frame {opacity: 1; transform: translateY(0);}

.sunrise-outer.cursor-default .sunrise-inner {cursor: default;}
.sunrise-outer.cursor-default .sunrise-scene {cursor: default;}

@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
    /* IE 10, IE 11 */
    .sunrise-inner {display: table; width: 100%; height: 100%;}
    .sunrise-scene {display: table-cell; vertical-align: middle; text-align: center;}
    .sunrise-frame {display: inline-block; text-align: left;}
}

결과 화면

모서리가 동글동글한 것이 마음에 든다!



본격적인 기능 구현 시작

draw popup

Sunrise.prototype.drawPopup = function () {
    var _ = this;

    if (_.options.ajax) {
        $.ajax({
            url: _.options.target,
            timeout: 10000,
            dataType: _.options.dataType,
            success: function (popup) {
                _.element.target = $(popup);
                _.options.openCallback(_.element.target);
                _.element.popup = _.element.target.appendTo(_.element.frame).show();
                _.element.outer.addClass('sunrise-visible');
                _.saveDefaultValue();
            }
        });
    } else {
        _.options.openCallback(_.element.target);
        _.element.popup = _.element.target.appendTo(_.element.frame).show();
        _.element.outer.addClass('sunrise-visible');
        _.saveDefaultValue();
    }
}

close popup

Sunrise.prototype.closePopup = function () {
    var _ = this;

    _.closeConfirmCheck();

    if (!_.initial.isClose) {
        _.initial.isClose = confirm(_.options.closeConfirmText);
    }
    if (_.initial.isClose) {
        if (!_.options.ajax) {
            _.element.popup.appendTo(_.element.body).hide();
            if (_.options.closeConfirm) {
                $.each(_.options.closeConfirm, function (i) {
                    _.element.popup.find('[name='+this+']').val(_.initial.saveDefaultValue[i]);
                });
            }
        }
        _.options.closeCallback(_.element.target);
        _.element.outer.remove();
        _.element.body.removeClass('sunrise-fixed');
        $(window).scrollTop(_.initial.scrollTop);
    }
}

events register

Sunrise.prototype.eventsRegister = (function () {
    return {
        close: function (_) {
            if (_.options.backgroundClose) {
                $(document).on(_.events.click, function (e) {
                    if (e.target === _.element.inner[0] || e.target === _.element.scene[0]) {
                        _.closePopup();
                    }
                });
            }
        }
    }
})();

아직은 이벤트가 close밖에 없지만 추후에 추가될 것을 대비하여 확장성을 고려한 모습



외부 소스에서 method를 실행할 수 있도록 구문 추가

Sunrise.prototype.sunrise = function (method) {
    var _ = this;

    if (_[method]) {
        _[method](arguments[1]);
    } else {
        console.log('This is not the method of Sunrise.');
    }
}

외국인개발자들이 사용해 주지 않을까 하는 기대감에 에러문구를 영어로 넣어보았다.



그 외 각종 유틸함수 추가

Sunrise.prototype.cursorChecker = function () {
    var _ = this;

    if (!_.options.backgroundClose) {
        _.element.outer.addClass('cursor-default');
    }
}

Sunrise.prototype.saveDefaultValue = function () {
    var _ = this;

    if (_.options.closeConfirm) {
        $.each(_.options.closeConfirm, function () {
            _.initial.saveDefaultValue.push(_.element.popup.find('[name='+this+']').val());
        });
    }
}

Sunrise.prototype.closeConfirmCheck = function () {
    var _ = this;

    if (_.options.closeConfirm) {
        $.each(_.options.closeConfirm, function (i) {
            if (_.element.popup.find('[name='+this+']').val() != _.initial.saveDefaultValue[i]) {
                _.initial.isClose = false;
                return false;
            }
        });
    }
}




끝으로 init에 함수실행

Sunrise.prototype.init = function () {
    var _ = this;

    _.layoutMarkup();
    _.cursorChecker();
    _.drawPopup();

    _.eventsRegister.close(_);
}




3. 결과화면

일반 팝업





화면보다 긴 팝업





닫을때 confirm 하기





4. 마치며

처음 빈파일을 만들고 시작할때는 막막하다가도 하나씩 해결해 나가는 재미에 계속 플러그인을 제작하게 되는것같다. 만약 기성플러그인들의 영문API를 보는것에 지쳤다면 간단한 플러그인부터 직접 개발해볼것을 강력히 추천하는 바이다.
아울러 항상 나에게 산넘어산을 안겨주시는 팀장님 이하 팀원들에게 감사의 말씀 전하고싶다.

※ 해당 소스는 내 개인 github에 올려놓고 계속해서 업데이트 할 예정이다.
github url : https://github.com/simplizm-company/sunrise

끝.

1 thought on “레이어팝업 플러그인 제작기

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다