L o a d i n g

(주) 빌리고

Category
개발
Customer
빌리고
Date
2022
내용
서버 관리, 코드 최적화, 관리자 개발, 프론트 앤드 개발
1. 메인페이지 데이터 비동기 작업

카테고리 순서 관리 기능 구현:
관리자 페이지를 생성하여 카테고리 순서를 변경할 수 있는 인터페이스를 제공합니다.
Handlebars.js를 활용하여 관리자가 카테고리 순서를 동적으로 조정할 수 있는 기능을 추가합니다.
변경된 순서는 서버에 저장되어 메인 페이지에 적용됩니다.

스크롤 이벤트에 의한 데이터 동적 로드 구현:
ScrollToPlugin 및 ScrollMagic.js를 사용하여 스크롤 이벤트를 감지하고, 일정 위치에 도달했을 때 추가 데이터를 비동기적으로 로드합니다.
이를 위해 스크롤 위치에 따라 서버로부터 데이터를 요청하는 API 엔드포인트를 생성하고, 해당 데이터를 페이지에 동적으로 추가합니다.
ScrollMagic.js를 활용하여 스크롤 위치를 감지하고, 스크롤 이벤트를 트리거합니다.

성능 최적화 및 사용자 경험 개선:
이미지나 데이터 로딩 시 적절한 캐싱 기법을 사용하여 성능을 최적화합니다.
웹 페이지 스크롤 시 자연스러운 경험을 제공하기 위해 애니메이션 및 로딩 효과를 추가합니다.
사용자가 로딩 중임을 인지할 수 있도록 UI/UX 요소를 개선하여 사용자 경험을 향상시킵니다.

js

const mobileMain = (function () { Handlebars.registerHelper('ifDivision', function (arg1) { return (((arg1 + 1) % 2) === 0); }); Handlebars.registerHelper('numberformat', function (number) { return new Intl.NumberFormat('ko-KR').format(number) }); Handlebars.registerHelper('isContain', function (str, contain) { return (str.indexOf(contain) > -1); }) let popup = $(".plan_popup_box, .dim"); let sectionBox = $(".main_cate_box_01"); let headerSlider = $('.main_visual > .swiper-container'); let bestCateNav = $('.main_bestCate'); let categorySectionItems = $('.main_module_best > .module_item'); let categoryData = [{"bestCategory":"029","bestTitle":"\uacb0\ud569\uc0c1\ud488<\/em>"},{"bestCategory":"008","bestTitle":"TV\/\ub514\uc9c0\ud138<\/em>"},{"bestCategory":"004","bestTitle":"\ub0c9\uc7a5\uac00\uc804<\/em>"},{"bestCategory":"009","bestTitle":"\uc138\ud0c1\uac00\uc804<\/em>"},{"bestCategory":"005","bestTitle":"\uac74\uac15\/\ubdf0\ud2f0<\/em>"},{"bestCategory":"001","bestTitle":"\uc0dd\ud65c\uac00\uc804<\/em>"},{"bestCategory":"002","bestTitle":"\uacc4\uc808\/\ud658\uacbd<\/em>"},{"bestCategory":"006","bestTitle":"\uc8fc\ubc29\uac00\uc804<\/em>"},{"bestCategory":"003","bestTitle":"\uac00\uad6c\/\uce68\ub300<\/em>"},{"bestCategory":"010","bestTitle":"\ub808\uc800\/\uc720\uc544\ub3d9<\/em>"},{"bestCategory":"043","bestTitle":"\uc5d0\uc5b4\ucee8<\/em>"},{"bestCategory":"0","bestTitle":"0\uc6d0\ub80c\ud0c8<\/em>"}]; let currentIdx = 0; let sectionData = categoryData[currentIdx]; let scene; let controller = new ScrollMagic.Controller(); let onLoading = []; let itemTemplate = Handlebars.compile($("#template-category-section").html()); let isLoading = false; function getItems(code, fn) { if ($('#' + code).find('.module_item').length > 0) return; if (typeof categoryData[currentIdx] === 'undefined') return; if(code != 0){ $.ajax({ type: 'GET', url: '/api/v2/models', data: {'ca_id': code, 'page_rows': 8}, dataType: 'json', global: false, beforeSend: function () { isLoading = true; }, success: function (res) { isLoading = false; fn(res); }, }); }else{ $.ajax({ type: 'GET', url: '/api/v2/models', data: {'ca_id': code, 'page_rows': 8}, dataType: 'json', global: false, beforeSend: function () { isLoading = true; }, success: function (res) { isLoading = false; fn(res); }, }); } } function drawItems(data) { if (typeof categoryData[currentIdx] === 'undefined') return; let offsetObj = $('#' + data.ca_id); offsetObj.find('.page-load-wrap').show(); let sectionHTML = itemTemplate({'sectionData': sectionData, 'Lists': data.Lists}); offsetObj.find('.page-load-wrap').hide(); sectionBox.find('#' + data.ca_id).append(sectionHTML); } function initSwiper() { headerSlider = new Swiper(headerSlider, { slidesPerView: 1, loop: true, speed: 500, pagination: { el: '.swiper-pagination', type: 'fraction', }, autoplay: { delay: 4000, disableOnInteraction: false, }, }); categorySectionItems = new Swiper(categorySectionItems, { slidesPerView: 'auto', spaceBetween: 10, freeMode: true, }); bestCateNav = new Swiper(bestCateNav, { slidesPerView: 'auto', spaceBetween: 10, freeMode: true, }); } function initTrigger() { // Run through all sections let items = $('.main_cate_box_01 .main_module_best'); $.each(items, function (key, val) { let height = $(val).height(); //height of current element scene = new ScrollMagic.Scene({ duration: height + 5, triggerElement: val, triggerHook: 1, reverse: true }) .on("enter", function (event) { // categoryNav.slideTo(key, 500); if (onLoading.indexOf(categoryData[key].bestCategory) > -1) return; onLoading.push(categoryData[key].bestCategory); getItems(categoryData[key].bestCategory, function (data) { drawItems(data); currentIdx++; }); }) // .addIndicators({name: " - begin "}) // Testing .addTo(controller); }) } function initSection() { let requests = []; for (const categoryDatum of categoryData) { requests.push(new Promise(done => { getItems(categoryDatum.bestCategory, function (data) { drawItems(data); }); return done; })); } Promise.all(requests); } // 쿠키 가져오기 let getCookie = function (cname) { let name = cname + "="; let ca = document.cookie.split(';'); for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') c = c.substring(1); if (c.indexOf(name) !== -1) return c.substring(name.length, c.length); } return ""; } // 24시간 기준 쿠키 설정하기 let setCookie = function (cname, cvalue, exdays) { let todayDate = new Date(); todayDate.setTime(todayDate.getTime() + (exdays * 24 * 60 * 60 * 1000)); let expires = "expires=" + todayDate.toUTCString(); document.cookie = cname + "=" + cvalue + "; " + expires; } let couponClose = function () { if (popup.find("input[type=checkbox]").prop("checked") === true) { setCookie("close", "Y", 1); //기간( ex. 1은 하루, 7은 일주일) } popup.hide(); } function openPop() { let cookiedata = document.cookie; if (cookiedata.indexOf("close=Y") < 0) { popup.show(); } else { popup.hide(); } popup.find('.off_box').click(function () { couponClose(); }); } return { init: function () { $(function () { initTrigger(); // initSection(); openPop(); }); } }; })(jQuery); mobileMain.init();

html


<div class="main_cate_box_01">
    <div id="029" class="section">
        <div class="main_prodList inner">
            <div class="prodList_tit">
                <h2 class="ff_NSR"><span><em>결합상품</em></span></h2>
            </div>
            <div class="prodList_list prod_list ">
                <div class="main_module_best">
                </div>
            </div>
            <div class="page-load-wrap section">
                <div class="page-load"></div>
                    <p class="ff_NSR"><em>로딩중입니다.</em><br>잠시만 기다려 주세요 :)</p>
            </div>
        </div>
    </div>
</div>

<div class="main_prodList inner">
    <div class="prodList_list prod_list">
        <ul class="col4">
            {{#each Lists}}
            <li>
                <div class="prod_box box sdw rnd12">
                    <a href="{{this.model_url}}">
                        <div class="photo">
                            <img src="{{this.model_thumnail_url}}" />
                            <div class="flag_water_wrap">{{{this.model_icons}}}</div>
                                                    </div>
                        <div class="info">
                            {{#if model_icons}}
                            <div class="flag_wrap">{{{this.model_icons}}}</div>
                            {{/if}}
                            <div class="prd_tit">
                                <span class="model ff_Play">{{this.model}}</span>
                                <h3>{{this.model_name}}</h3>
                            </div>
                            <div class="prd_prc ff_Play">
                                <dl class="prc">
                                    <dt>{{this.model_monthly_string}}</dt>
                                    <dd><em>{{numberformat this.goods_price}}</em>원</dd>
                                </dl>
                                <dl class="card">
                                    <dt>{{this.model_alliance_string}}</dt>
                                    <dd><em>{{numberformat this.model_sale_price}}</em>원</dd>
                                </dl>
                            </div>
                        </div>
                    </a>
                </div>
            </li>
            {{/each}}
        </ul>
    </div>
</div>
                            
2. 비동기식 카테고리 필터

HTML

{{#each FilterList}}
    {{#if (isNotEmpty value)}}
        {{#if (isSame param 'catecode')}}<!-- 1차카테고리 -->
            <div class="ft_row"><h3>{{ name }}</h3>
                <ul>
                    {{#each value}}
                        <li class="{{../class}}">
                            <a href="javascript:;" data-target="filter_{{../param}}%5B%5D" data-value="{{@key}}" data-multiple="{{../isMultiple}}">{{catename}}</a>
                        </li>
                    {{/each}}
                </ul>
            </div>
            <div class="ft_row sub" style="display:none;"><h3>하위 카테고리</h3>
                <ul>
                {{#each value}}
                    {{#each children}}
                        <li class="parent_{{upcate}}" style="display:none;">
                            <a href="javascript:;" data-target="filter_catecode%5B%5D" data-value="{{catecode}}" data-multiple="{{../isMultiple}}">{{catename}}</a>
                        </li>
                    {{/each}}
                {{/each}}
                </ul>
            </div>
        {{else}}
        <div class="ft_row" data-display="30"><h3>{{ name }}</h3>
            <ul class="right_list">
                {{#each value}}
                    {{#if ../isReorder }}
                        <li class="js-load item {{../class}} {{#compare @index 30 operator='<'}} {{/compare}} {{ isImportant ../this id }}"><a href="#" data-target="filter_{{../param}}%5B%5D" data-value="{{id}}" data-multiple="{{../isMultiple}}">{{{name}}}</a></li>
                    {{else}}
                        <li class="js-load item {{../class}} {{#compare @index 30 operator='<'}} {{/compare}} {{ isImportant ../this @key }}"><a href="#" data-target="filter_{{../param}}%5B%5D" data-value="{{@key}}" data-multiple="{{../isMultiple}}">{{{this}}}</a></li>
                    {{/if}}
                {{/each}}
            </ul>
            {{#compare value 30 operator='>'}}
            <div class="btn-wrap btn_more active"> <a href="javascript:;" class="button"><span>더보기</span> <i class="ico_arrow_down_b"></i></a> </div>
            {{/compare}}
        </div>
        {{/if}}
    {{/if}}
{{/each}}   

js

var regexPage   = /&page=\w+/g;
var regexFilter = /&filter_[a-z]+(\[\]|%5B%5D)=+[A-z,\|,\,,\*,\-,0-9]+/g;

// 정규식부분 처리해야함.
var regexSort   = /&sort=+[A-z, \.]+/g;
var regexSorder = /&sortodr=+[A-z]+/g;

var filterAttr = [];
var enableFilter = false;
var onSub = false;

Handlebars.registerHelper('numberformat', function (val) {
    return parseInt(val).toLocaleString();
})
Handlebars.registerHelper('isNotEmpty', function (obj) {
    return (obj !== null);
})
Handlebars.registerHelper('isSame', function (val1, val2) {
    return (val1 == val2);
})
Handlebars.registerHelper('isContain', function (str, contain) {
    return (str.indexOf(contain) > -1);
})
Handlebars.registerHelper('isImportant', function (data, val) {
    if (typeof data.highlight !== 'undefined') {
        return (data.highlight.indexOf(parseInt(val)) > -1) ? " filter_important":"";
    }
})
Handlebars.registerHelper('compare', function(lvalue, rvalue, options) {
    if (typeof lvalue === 'object') {
        lvalue = Object.keys(lvalue).length;
    }

    if (arguments.length < 3)
        throw new Error("Handlerbars Helper 'compare' needs 2 parameters");

    operator = options.hash.operator || "==";

    var operators = {
        '==':       function(l,r) { return l == r; },
        '===':      function(l,r) { return l === r; },
        '!=':       function(l,r) { return l != r; },
        '<':        function(l,r) { return l < r; },
        '>':        function(l,r) { return l > r; },
        '<=':       function(l,r) { return l <= r; },
        '>=':       function(l,r) { return l >= r; },
        'typeof':   function(l,r) { return typeof l == r; }
    }

    if (!operators[operator])
        throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);

    var result = operators[operator](lvalue,rvalue);

    if( result ) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }
});
Handlebars.registerHelper('displayFuneralServiceAgency', function (goodsType) {
    return (goodsType === 'F');
});
$(function() {


    // Filter Reset
    $("#filterReset").click(function() {
        $(".filter_area > .ft_row li").removeClass("on");
        $(".filter_area > .ft_row.sub li").removeClass("on");
        $(".filter_area > .ft_row.sub").hide();

        var renewUrl = location.href;
        renewUrl     = renewUrl.replace(regexPage, "");
        renewUrl     = renewUrl.replace(regexFilter, "");

        // Parameter & url setting
        var parameter = renewUrl.split("?");
        history.pushState(null, null, renewUrl);

        // Models Requests Lists
        modelsRequests(parameter[1]);
    })
});


// Models Requests Lists
function modelsRequests(obj) {

    var Parameters = new Array();

    if($.type(obj) == "object" || $.type(obj) == "array") {
        $.each(obj, function(key, val) {
            Parameters.push(key + "=" + val);
        });
    }
    else if($.type(obj) == "string") {
        $.each(obj.split("&"), function(key, val) {
            Parameters.push(val);
        });
    }

    Parameters.push("section=models")
    var ResponseData    = "";

    //Ajax call
                var AjaxUrl         = "/api/v2/models";
                var AjaxParameter   = Parameters.join("&");
    var AjaxType        = "GET";
    var AjaxDataType    = "JSON";
    var AjaxAsync       = false;
    var AjaxError       = "";
    var AjaxComplete    = "";
    var AjaxSuccess     = function(data) {
        modelsResponse(data);
        //ResponseData = data;
    };

    AjaxLoadAni(AjaxUrl, AjaxParameter, AjaxType, AjaxDataType, AjaxAsync, AjaxSuccess, AjaxError, AjaxComplete);

    return ResponseData ? ResponseData : "";
}


function initSmartFilter(data) {
    if(enableFilter) return;
    for (let i = 0; i < data["FilterList"].length; i++) {
        if (data["FilterList"][i]["name"] == "상조사") {
            const tmp = data["FilterList"][0];
            data["FilterList"][0] = data["FilterList"][1];
            data["FilterList"][1] = tmp;
        }
    }
    var template = Handlebars.compile($("#template-filter-section").html());
    $('.smart_filter .filter_area').html(template(data));
    const subRow = $('.ft_row.sub');
    // Smart Filter
    $('.ft_row > ul > li a').click(function(e) {
        e.preventDefault();
        var self = $(this);
        var isOn = $(this).parent('li').hasClass('on');
        var parentVal = $(this).data('parent');
        var selectedVal = $(this).data('value');
        var selectedTarget = $(this).data('target');
        var isMultiple = $(this).data('multiple');
        $(this).parent('li').toggleClass('on');
        var renewUrl = location.href;
        renewUrl     = renewUrl.replace(regexPage, "");
        renewUrl     = renewUrl.replace(regexFilter, "");

        if (isMultiple != true) {
            if ($(this).closest('ul').find('li.on').length > 1) {
                $(this).closest('ul').find('li').removeClass('on');
                $(this).parent('li').toggleClass('on');
            }
        }
        if (selectedTarget === 'filter_catecode%5B%5D' || selectedTarget === 'filter_catecode[]') {
            if ($(this).closest('ul').find('li.on').length > 0 && selectedVal !== "059" && selectedVal !== "070") {
                if (!$(this).closest('.ft_row').hasClass('sub')) {
                    subRow.find('li').removeClass('on').hide();
                    subRow.show();
                    subRow.find('.parent_' + selectedVal).stop().fadeIn();
                }
            }else if(!$(this).closest('.ft_row').hasClass('sub')) {
                subRow.find('li').removeClass('on').hide();
                subRow.hide();
            }
        }
        var filterParameter = new Array();
        if (parentVal) {
            var attr = _.find(filterAttr, parentVal);
            if (isOn) {
                if (typeof attr !== 'undefined') {
                    var filterAttrIndex = _.findIndex(filterAttr, parentVal);
                    attr[parentVal].push(selectedVal);
                    filterAttr[filterAttrIndex] = attr;
                }else{
                    var data = {};
                    data[parentVal] = [parseInt(selectedVal)];
                    filterAttr.push(data)
                }
            }else{
                _.each(filterAttr, function (attr) {
                    if (_.has(attr, parentVal)) {
                        _.remove(attr[parentVal], function (v) {
                            return v === selectedVal;
                        });
                        if (_.isEmpty(attr[parentVal])) {
                            _.remove(filterAttr, attr);
                        }
                    }
                });
            }
            filterAttr = filterAttr.filter(function(){return true;});

            var attrParams = "";
            var result = _.chain(filterAttr)
                .map(function(n){
                    return _.first(_.keys(n))+"|"+_.join(_.values(n));
                })
                .join('**').value();
            filterParameter.push(selectedTarget + "=" +result);
            if(filterAttr.length < 1){
                var attrIndex = filterParameter.indexOf('filter_attr%5B%5D=');
                filterParameter.splice(attrIndex,1)
            } //&& _.findIndex(filterParameter,'filter_attr[]=') > -1) renewUrl = renewUrl.replace("filter_attr[]=", "");
        }else{
            let isChecked = false;
            $.each($(".filter_area > .ft_row li"), function(key, val) {
                if($(this).hasClass("on") === true) {
                    var dataTatget = $(this).find("a").data("target");
                    var dataValue = $(this).find("a").data("value");
                    var dataParent = $(this).find("a").data("parent");
                    filterParameter.push(dataTatget + "=" +dataValue);
                    isChecked = true;
                }
            });
            if (!isChecked) {
                subRow.find('li').hide();
                subRow.hide();
            }
            $.each($(".filter_area > .ft_row.sub li"), function(key, val) {
                if($(this).hasClass("on") === true) {
                    var dataTatget = $(this).find("a").data("target");
                    var dataValue = $(this).find("a").data("value");
                    var dataParent = $(this).find("a").data("parent");
                    if (dataTatget === 'filter_catecode%5B%5D' || dataTatget === 'filter_catecode[]') {
                        if (subRow.find('.parent_' + dataValue).length > 0) {
                            let children = null;
                            subRow.find('.parent_' + dataValue).each(function () {
                                if ($(this).hasClass("on") === true) {
                                    children = $(this).children("a");
                                }
                            });
                            if (children !== null) {
                                dataTatget = children.data('target');
                                dataValue = children.data('value');
                                filterParameter.push(dataTatget + "=" + dataValue);
                                onSub = true;
                                return false;
                            }
                        }
                    }
                }
            });
        }

        if(filterParameter.length > 0) renewUrl = renewUrl + "&" + filterParameter.join("&");

        // Parameter & url setting
        var parameter = renewUrl.split("?");
        history.pushState(null, null, renewUrl);

        // Models Requests Lists
        modelsRequests(parameter[1]);

    });
    // Sorting Models
    $('.sorting > ul > li').click(function(e) {
        e.preventDefault();

        $(this).parent('li label').toggleClass('on');

        var renewUrl = location.href;
        renewUrl     = renewUrl.replace(regexPage, "");
        renewUrl     = renewUrl.replace(regexFilter, "");
        renewUrl     = renewUrl.replace(regexSort, "");
        renewUrl     = renewUrl.replace(regexSorder, "");

        var filterParameter = new Array();

        // Check Filter
        $.each($(".filter_area > .ft_row li"), function(key, val) {
            if($(this).hasClass("on") === true) {

                var dataTatget = $(this).children("a").attr("data-target");
                var dataValue = $(this).children("a").attr("data-value");

                filterParameter.push(dataTatget + "=" + dataValue);
            }
        });

        // Check Sort
        $.each($(".sorting > ul > li"), function(key, val) {
            if($(this).find("label").hasClass("on") === true) {

                var dataTatget = $(this).attr("data-target");
                var dataValue = $(this).attr("data-value");

                if(dataTatget && dataValue) filterParameter.push("sort=" + dataTatget + "&sortodr=" + dataValue);
            }
        });

        if(filterParameter.length > 0) renewUrl = renewUrl + "&" + filterParameter.join("&");

        // Parameter & url setting
        var parameter = renewUrl.split("?");
        history.pushState(null, null, renewUrl);

        // Models Requests Lists
        modelsRequests(parameter[1]);

    });
    enableFilter = true;

    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);
    const
        keys = urlParams.keys(),
        values = urlParams.values(),
        entries = urlParams.entries();

    for (const entry of entries) {
        let targetObj = $(`[data-target="${entry[0]}"]`);
        let valueObj = $(`[data-value="${entry[1]}"]`);
        $('.smart_filter .filter_area .ft_row ul > li a').each(function (e) {
            if (($(this).data('target') === entry[0] && $(this).data('value') == entry[1]) || $(this).data('target') === encodeURIComponent(entry[0]) && $(this).data('value') == encodeURIComponent(entry[1])) {
                $(this).closest('li').addClass('on');
            }
            if (decodeURIComponent(entry[0]) == 'filter_catecode[]' && subRow.find('.parent_' + entry[1]).length > 0) {
                subRow.find('li').hide();
                subRow.show();
                subRow.find('.parent_' + entry[1]).fadeIn();
            }
        });
    }
    // $('.btn_more').unbind('click').click(function(e){
    //     e.preventDefault();
    //     const row = $(this).closest('.ft_row');
    //     const displayCount = row.data('display');
    //     const list = row.find('.right_list');
    //     const activeCount = list.find('li.active').length;
    //     if (activeCount <= displayCount) {
    //         list.find('li').addClass('active');
    //     }else{
    //         list.find(`li:gt(${displayCount-1})`).removeClass('active');
    //     }
    //     if($(this).find('i').hasClass('ico_arrow_up_b')){
    //         $(this).find('i').removeClass('ico_arrow_up_b');
    //         $(this).find('i').addClass('ico_arrow_down_b');
    //     } else {
    //         $(this).find('i').removeClass('ico_arrow_down_b');
    //         $(this).find('i').addClass('ico_arrow_up_b');
    //     }
    //     $(this).find('span').text((activeCount > displayCount) ? '더보기' : '접기');
    // });


    const brand = $('.ft_row ul li:nth-child(1).brand').parent('ul');
    const brandHeight = brand.outerHeight();

    if (brandHeight > 96) {
        brand.addClass('fold');
        $('.btn_more').addClass('active');
        $('.btn_more').click(function () {
            if (brand.hasClass('fold')) {
                brand.removeClass('fold');
                $(this).find('span').text('접기');
                $(this).find('i').addClass('ico_arrow_up_b');
                $(this).find('i').removeClass('ico_arrow_down_b');
            } else {
                brand.addClass('fold');
                $(this).find('span').text('더보기');
                $(this).find('i').addClass('ico_arrow_down_b');
                $(this).find('i').removeClass('ico_arrow_up_b');
            }
        });
    }

}

var modelsResponse = function(data) {
    if (data.Lists.length < 1) {
        $('.prodList_none').show();
    }else{
        $('.prodList_none').hide();
    }
    var modelLists = "";
    var template = Handlebars.compile($("#template-goods").html());
    $(".prodList_wrap > .prodList_list > ul").html(template(data.Lists));

    // Lists Counts Output
    $(".prodList_wrap > .prodList_sort > h3 > .txt_org").html(data.Counts);

    // Pagination Output
    $(".prodList_wrap > .paging").html(data.Paginations);
    initSmartFilter(data);
}

modelsRequests('ca_id=034');

//필터 접기/펴기 부분
//필터 접기/펴기 부분
$('.popup_close_btn').click(function (){
    $('.list_popup_banner').fadeOut(300);
});

$('.list_best_item').slick({
    slidesToShow: 4,
    slidesToScroll: 4,
    dots: true,
    dotsClass: 'custom_paging ff_Play',
    customPaging: function (slider, i) {
        return  '<em>' + (i + 1) + '</em>' + '/' + Math.ceil(slider.slideCount / 4);
    }
});
3. 사이트 신규 메뉴 개발 (0원 렌탈, 브랜드 샵, 상조)