end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

jqueryやbootstrap非依存な responsive & tab menu & carousel html

responsive html の template - end0tknr's kipple - web写経開発

上記entryの改訂版で、githubにも commitしています。

https://end0tknr.github.io/sandbox/responsive/

目次

html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>end0tknr</title>
  <!-- hamburger menu や 検索icon の為、google material icons 使用 -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <link rel="stylesheet" href="css/common.css">
</head>
<body>

  <header>
    <div class="site_logo">
      <a href="">
    <img src="img/site_logo.svg"/>end0tknr
      </a>
    </div>

    <div id="pc_menu">
      <nav>
        <ul>
          <li><a href="#">メニュー 1</a></li>
          <li><a href="#">メニュー 2</a></li>
          <li><a href="#">メニュー 3</a></li>
          <li><a href="#">メニュー 4</a></li>
          <li><a href="#">メニュー 5</a></li>
          <li><a href="#">メニュー 6</a></li>
          <li>
        <a href="#">
          <span class="material-icons">search</span>
        </a>
      </li>
        </ul>
      </nav>
    </div><!--#pc_menu-->
    
    <div id="sp_menu_open_container">
      <span class="material-icons" id="sp_menu_open">menu</span>
    </div>

    <div id="sp_menu_overlay">
      <span class="material-icons" id="sp_menu_close">close</span>
      <nav><!-- #pc_menu の内容を jsで copy します -->
      </nav>
    </div>
  </header>

  <section class="carousel">
    <div>
      <ul>
        <li><img src="img/pic1.png"></li>
        <li><img src="img/pic2.png"></li>
        <li><img src="img/pic3.png"></li>
        <li><img src="img/pic4.png"></li>
      </ul>
      <button class="carousel_prev">&laquo;</button>
      <button class="carousel_next">&raquo;</button>
    </div>
    <nav></nav>
  </section><!--.carousel-->
  
  <section class="carousel">
    <div>
      <ul>
        <li><img src="img/pic1.png"></li>
        <li><img src="img/pic2.png"></li>
        <li><img src="img/pic3.png"></li>
        <li><img src="img/pic4.png"></li>
      </ul>
      <button class="carousel_prev">&laquo;</button>
      <button class="carousel_next">&raquo;</button>
    </div>
    <nav></nav>
  </section><!--.carousel-->



  <section class="body">
    <div class="image">image</div>
    <div class="text">text</div>
  </section>
  

  <div class="tab_menu">
    <ul>
      <li><a href="#" data-id="" class="active">サイトの概要</a></li>
      <li><a href="#" data-id="">サービス内容</a></li>
      <li><a href="#" data-id="">お問い合わせ</a></li>
    </ul>
    
    <section class="active">
      サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。
      サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。
    </section>
    
    <section>
      サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。
      サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。
    </section>
    
    <section>
      お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。
      お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。
    </section>
  </div><!-- .tab_menu -->

  <div class="tab_menu">
    <ul>
      <li><a href="#" data-id="" class="active">サイトの概要</a></li>
      <li><a href="#" data-id="">サービス内容</a></li>
      <li><a href="#" data-id="">お問い合わせ</a></li>
    </ul>
    
    <section class="active">
      サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。
      サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。
    </section>
    
    <section>
      サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。
      サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。
    </section>
    
    <section>
      お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。
      お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。
    </section>
  </div><!-- .tab_menu -->

  <script src="js/common.js"></script>
</body>
</html>

css

body {
    margin: 0;
    font-family: sans-serif;  /* TODO font指定 */
}

header {
    display: flex;
    padding: 0 16px;
}

.site_logo img {
    vertical-align: middle;
    margin-right:   10px;
}
.site_logo a {
    margin: 0;
    line-height: 64px;
    font-size:   32px;
    font-weight: bold;
    color: #000;
    text-decoration: none;
    white-space: nowrap;
}

#sp_menu_open_container {
    margin-left: auto;
}

#sp_menu_open_container #open {
    font-size:   32px;
    line-height: 64px;
    cursor: pointer;
}

#sp_menu_open_container #open.hide {
  display: none;
}

#sp_menu_overlay {
    position: fixed;
    top:    0;
    bottom: 0;
    right:  0;
    left:   0;
    background: rgba(255, 255, 255, 0.95);
    text-align: center;
    padding:    64px;
    opacity:    0;
    pointer-events: none; /* click無効化 */
    transition: opacity .6s;
    z-index: 100;
}

#sp_menu_overlay.show {
    opacity: 1;
    pointer-events: auto;
}

#sp_menu_overlay #close {
    position: absolute;
    top:   16px;
    right: 16px;
    font-size: 32px;
    cursor: pointer;
}

#sp_menu_overlay ul {
    list-style-type: none;
    margin:  0;
    padding: 0;
}

#sp_menu_overlay li {
    margin-top: 24px;
    opacity: 0;
    transform: translateY(16px);
    transition: opacity .3s, transform .3s;
}

#sp_menu_overlay.show li {
    opacity: 1;
    transform: none;
}

#pc_menu {
    display: none;
}

.body .image {
    background: pink;
    height: 100px;
}

.body .text {
    background: silver;
    height: 100px;
}

.carousel {
    width:    80%;
    margin:   16px auto;
}
    
.carousel div {
  width: 100%;
  height: 220px;
  overflow: hidden;
  position: relative;
}

.carousel ul {
    list-style: none;
    margin:  0;
    padding: 0;
    height: 100%;
    display: flex;
    transition: transform .3s;
}

.carousel li {
    height: 100%;
    min-width: 100%;
}

.carousel li img {
    width:  100%;
    height: 100%;
    object-fit: cover;
}

.carousel_prev, .carousel_next {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    border: none;
    background: rgba(0, 0, 0, .8);
    color: #fff;
    font-size: 24px;
    padding: 0 8px 4px;
    cursor: pointer;
}

.carousel_prev:hover, .carousel_next:hover {
    opacity: .8;
}

.carousel_prev {
    left: 0;
}

.carousel_next {
    right: 0;
}
.hidden {
    display: none;
}

.carousel nav {
    margin-top: 16px;
    text-align: center;
}

.carousel nav button + button {
    margin-left: 8px;
}

.carousel nav button {
    border: none;
    width: 16px;
    height: 16px;
    background: #ddd;
    border-radius: 50%;
    cursor: pointer;
}

.carousel nav .current {
    background: #999;
}


.tab_menu {
  margin: 30px auto;
  width: 500px;
}

.tab_menu ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
}

.tab_menu ul li a {
  display: inline-block;
  width: 100px;
  text-align: center;
  padding: 8px 0;
  color: #333;
  text-decoration: none;
  border-radius: 4px 4px 0 0;
}

.tab_menu ul li a.active {
  background: #333;
  color: #fff;
}

.tab_menu ul li a:not(.active):hover {
  opacity: 0.5;
  transition: opacity 0.4s;
}

.tab_menu section.active {
  background: #333;
  color: #fff;
  min-height: 150px;
  padding: 12px;
  display: block;
}

.tab_menu section {
  display: none;
}

.body .image {
    background: pink;
    height: 100px;
}

.body .text {
    background: silver;
    height: 100px;
}

@media (min-width: 600px) {
    
    #pc_menu {
    display: block;
    margin-left: auto;
    }
    #pc_menu ul {
    list-style-type: none;
    margin:  0;
    padding: 0;
    display: flex;
    }

    #pc_menu span {
    line-height:  64px;
    }
    #pc_menu a {
    display: block;
    line-height:  64px;
    margin-right: 20px;
    color: #000;
    text-decoration: none;
    white-space: nowrap;
    }
    #pc_menu a:hover {
    background: #f2f2f2;
    }
    #sp_menu_open_container {
    display: none;
    }

    
    section.body {
    display: flex;
    }
    .image {
    width: 200px;
    }
    .text {
    flex: 1;
    }
}

@media (min-width: 980px) {

    header {
    width:  980px;
    margin: 0 auto;
    }
}

javascript

'use strict';

{
    var WindowBase = function() {};

    WindowBase.prototype = {
        
        init_window: function(){
            this.init_sp_menu();
            
            var tab_menus = document.querySelectorAll('.tab_menu');
            tab_menus.forEach(tab_menu=>{
                this.init_tab_menu( tab_menu );
            });
            
            var carousels = document.querySelectorAll('.carousel');
            carousels.forEach(carousel=>{
                this.init_carousel( carousel );
                this.update_carousel_buttons( carousel );
            });
            
            // カルーセルのサイズをwinサイズに追従
            window.addEventListener('resize', () => {
                carousels.forEach(carousel=>{
                    this.slide_carousel( carousel );
                });
            });

            
        },

        init_sp_menu: function(){
            var pc_menu_ul = document.querySelector('#pc_menu nav ul');
            var sp_menu_ul = pc_menu_ul.cloneNode(true);

            var sp_menu_nav = document.querySelector('#sp_menu_overlay nav');
            sp_menu_nav.after(sp_menu_ul);

            this.sp_menu_open   = document.getElementById('sp_menu_open');
            this.sp_menu_close  = document.getElementById('sp_menu_close');
            this.sp_menu_overlay= document.getElementById('sp_menu_overlay');

            this.sp_menu_open.addEventListener('click', () => {
                this.sp_menu_overlay.classList.add('show');
                this.sp_menu_open.classList.add('hide');
            });
            this.sp_menu_close.addEventListener('click', () => {
                this.sp_menu_overlay.classList.remove('show');
                this.sp_menu_open.classList.remove('hide');
            });
            
        },

        init_carousel: function(carousel){
            carousel.current_index = 0;
            carousel.prev_button = carousel.querySelector('.carousel_prev');
            carousel.next_button = carousel.querySelector('.carousel_next');
            carousel.ul     = carousel.querySelector('ul');
            carousel.slides = carousel.querySelectorAll('ul li');
            carousel.dots   = [];
            
            // ボタンによる左右スライド
            carousel.prev_button.addEventListener('click', () => {
                carousel.current_index -= 1;
                this.update_carousel_buttons(carousel);
                this.update_carousel_dots(carousel);
                this.slide_carousel(carousel);
            });
            carousel.next_button.addEventListener('click', () => {
                carousel.current_index += 1;
                this.update_carousel_buttons(carousel);
                this.update_carousel_dots(carousel);
                this.slide_carousel(carousel);
            });

            // カルーセル下部に表示する丸ボタン生成
            for (let i = 0; i < carousel.slides.length; i++) {
                var button = document.createElement('button');
                button.addEventListener('click', () => {
                    carousel.current_index = i;
                    this.update_carousel_dots(carousel);
                    this.update_carousel_buttons(carousel);
                    this.slide_carousel(carousel);
                });
                carousel.dots.push(button);
                carousel.querySelector('nav').appendChild(button);
            }
            
            carousel.dots[0].classList.add('current');
        },
        
        init_tab_menu: function(tab_menu){
            var this_obj = this;

            var tab_menu_items    = tab_menu.querySelectorAll('ul li a');
            var tab_menu_contents = tab_menu.querySelectorAll('section');

            for (let i = 0; i < tab_menu_items.length; i++) {
                tab_menu_items[i].addEventListener('click', event => {
                    event.preventDefault();
                    
                    tab_menu_items.forEach(item => {
                        item.classList.remove('active');
                    });
                    tab_menu_items[i].classList.add('active');
                    
                    tab_menu_contents.forEach(content => {
                        content.classList.remove('active');
                    });
                    tab_menu_contents[i].classList.add('active');
                });
            }
        },

        update_carousel_buttons: function (carousel) {
            carousel.prev_button.classList.remove('hidden');
            carousel.next_button.classList.remove('hidden');
        
            if (carousel.current_index == 0) {
                carousel.prev_button.classList.add('hidden');
                
            }
            if (carousel.current_index === carousel.slides.length - 1) {
                carousel.next_button.classList.add('hidden');
            }
        },

        slide_carousel: function(carousel) {
            var slide_w = carousel.slides[0].getBoundingClientRect().width;
            var current_index = carousel.current_index;
            
            carousel.ul.style.transform =
                `translateX(${-1 * slide_w * current_index}px)`;
        },
        
        update_carousel_dots: function(carousel) {
            carousel.dots.forEach(dot => {
                dot.classList.remove('current');
            });
            carousel.dots[carousel.current_index].classList.add('current');
        }
        
    };

    var win_base = new WindowBase();
    win_base.init_window();
}