bootstrap ライン風チャット画面を作る





成果物


デモ


index.html


index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>ライン風チャット画面</title>

    <!-- bootstrap  -->
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
      crossorigin="anonymous"
    />

    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <!-- bootstrapの謎の右の余白を消すために必要 -->
    <div id="wrap">
      <!-- header -->
      <header class="fixed-top">
        <!-- nav -->
        <nav class="navbar navbar-dark bg-dark">
          <div class="chat_title">
            プログラミング(3)
          </div>
          <button
            type="button"
            class="navbar-toggler"
            data-toggle="collapse"
            data-target="#navbarList"
            aria-controls="navbarList"
            aria-expanded="false"
            aria-label="ナビゲーションの切替"
          >
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarList">
            <ul class="nav navbar-nav ml-auto">
              <li class="list-group-item"></li>
            </ul>
          </div>
        </nav>
      </header>
      <!-- main -->
      <main>
        <div class="fixed-bg bg01">
          <br /><br />

          <div class="line-bc">
            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon"><img src="img/icon0.png" /><br /></div>
              <div class="chatting">
                <div class="says">
                  <p>
                    左コメント左コメント左コメント左コメント左コメント左コメント左コメント左コメント
                  </p>
                </div>
              </div>
            </div>

            <!--右コメント-->
            <div class="mycomment">
              <p>
                右コメント
              </p>
            </div>

            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon">
                <img src="img/icon0.png" />
              </div>
              <div class="chatting">
                <div class="says">
                  <p>左コメント</p>
                </div>
              </div>
            </div>

            <!--右コメント-->
            <div class="mycomment">
              <p>
                右コメント
              </p>
            </div>

            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon">
                <img src="img/icon0.png" />
              </div>
              <div class="chatting">
                <div class="says">
                  <p>左コメント</p>
                </div>
              </div>
            </div>

            <!--右コメント-->
            <div class="mycomment">
              <p>
                右コメント
              </p>
            </div>
            <!--右コメント-->
            <div class="mycomment">
              <p>
                右コメント
              </p>
            </div>

            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon">
                <img src="img/icon0.png" />
              </div>
              <div class="chatting">
                <div class="says">
                  <p>左コメント</p>
                </div>
              </div>
            </div>

            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon">
                <img src="img/icon0.png" />
              </div>
              <div class="chatting">
                <div class="says">
                  <p>左コメント</p>
                </div>
              </div>
            </div>

            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon">
                <img src="img/icon0.png" />
              </div>
              <div class="chatting">
                <div class="says">
                  <p>左コメント</p>
                </div>
              </div>
            </div>

            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon">
                <img src="img/icon0.png" />
              </div>
              <div class="chatting">
                <div class="says">
                  <p>左コメント</p>
                </div>
              </div>
            </div>
            <!--左コメント-->
            <div class="balloon6">
              <div class="opponent_name">
                たしろ
              </div>
              <div class="faceicon">
                <img src="img/icon0.png" />
              </div>
              <div class="chatting">
                <div class="says">
                  <p>左コメント</p>
                </div>
              </div>
            </div>

            <!--右コメント-->
            <div class="mycomment">
              <p>
                右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント
              </p>
            </div>

            <!--右コメント-->
            <div class="mycomment">
              <p>
                右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント右コメント
              </p>
            </div>
          </div>

          <br /><br /><br />

          <div id="sumaho" style="display: none; height: 300px;"></div>
        </div>

        <!-- footer -->
        <footer class="fixed-bottom bg-dark" id="footer_input">
          <div class="input-group">
            <textarea
              class="form-control"
              id="chat_textarea"
              placeholder="メッセージ"
            ></textarea>

            <span class="btn btn-primary" style="padding-top: 10px;">
              <img src="img/send.png" width="30" />
            </span>
          </div>
        </footer>
      </main>
    </div>

    <!-- bootstrapを使うのに必要なjavascript  -->
    <script
      src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
      integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
      integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
      integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
      crossorigin="anonymous"
    ></script>
    <!-- bootstrapを使うのに必要なjavascriptここまで  -->

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



css/style.css


css/style.css
:root {
  --footer-height: 50px;
}

body {
  margin: 0;
}

#wrap {
  /* bootstrapの謎の右の余白を消す */
  overflow: hidden;
  max-width: 768px;
  margin: 0 auto;
}

header {
  max-width: 768px;
  margin: 0 auto;
}

/* main */
main {
  display: flex;
  flex-direction: column;
}

.fixed-bg {
  min-height: 100vh;
  background-attachment: fixed;
  background-size: cover;
  background-position: center;
}

.bg01 {
  background: #88b3f7;
}

/* footer */
footer {
  max-width: 768px;
  height: var(--footer-height);
  margin: 0 auto;
  background-color: yellow;
}

.chat_title {
  color: #fff;
}

.chat_title_icon {
  width: 15px;
  height: 15px;
  border-radius: 10px;
}

#chat_textarea {
  height: var(--footer-height);
}

/* スマホ用 */
@media screen and (max-width: 767px) {
  #wrap {
    max-width: 500px;
  }
  footer {
    max-width: 500px;
    margin: 0 auto;
  }
}

/* https://saruwakakun.com/html-css/reference/speech-bubble#section7ここから */
/*以下、①背景色など*/
.line-bc {
  padding: 20px 10px;
  max-width: 768px;
  margin: 15px auto;
  text-align: right;
  font-size: 14px;
}

/*以下、②左側のコメント*/
.balloon6 {
  width: 100%;
  margin: 10px 0;
  overflow: hidden;
}

.balloon6 .opponent_name {
  color: #fff;
  text-align: left;
  font-size: 14px;
}

.balloon6 .faceicon {
  float: left;
  margin-right: -50px;
  width: 40px;
}

.balloon6 .faceicon img {
  width: 100%;
  height: auto;
  border-radius: 50%;
  border: 3px solid #fff;
}
.balloon6 .chatting {
  width: 100%;
  text-align: left;
}
.says {
  display: inline-block;
  position: relative;
  color: #fff;
  margin: 0 0 0 50px;
  padding: 10px;
  max-width: 250px;
  border-radius: 12px;
  background: #000;
}

.says:after {
  content: '';
  display: inline-block;
  position: absolute;
  top: 3px;
  left: -19px;
  border: 8px solid transparent;
  border-right: 18px solid #000;
  -webkit-transform: rotate(35deg);
  transform: rotate(35deg);
}
.says p {
  margin: 0;
  padding: 0;
}

/*以下、③右側の緑コメント*/
.mycomment {
  margin: 10px 0;
}
.mycomment p {
  display: inline-block;
  position: relative;
  margin: 0 10px 0 0;
  padding: 8px;
  max-width: 250px;
  border-radius: 12px;
  background: #30e852;
  font-size: 15px;
}

.mycomment p:after {
  content: '';
  position: absolute;
  top: 3px;
  right: -19px;
  border: 8px solid transparent;
  border-left: 18px solid #30e852;
  -webkit-transform: rotate(-35deg);
  transform: rotate(-35deg);
}
/* https://saruwakakun.com/html-css/reference/speech-bubble#section7ここまで */



js/index.js


js/index.js
let scrollTop = 0;

/**
 * https://qiita.com/Kyou13/items/bf9bcfb84431d7545fc2
 * スクロールを最下部に移動する方法
 */
const move_bottom = () => {
  var element = document.documentElement;
  var bottom = element.scrollHeight - element.clientHeight;
  window.scroll(0, bottom);
};

/**
 * https://qiita.com/s-yoshiki/items/9cab4e73dd677442469a
 */
const isAndroid = () => {
  if (navigator.userAgent.indexOf('Android') > 0) {
    return true;
  }

  return false;
};

document.getElementById('footer_input').addEventListener(
  'focus',
  event => {
    // css変数操作
    document.documentElement.style.setProperty('--footer-height', '100px');

    if (isAndroid()) {
      scrollTop = document.documentElement.scrollTop;
      document.getElementById('sumaho').style.display = 'block';
      move_bottom();
    }
  },
  true
);

document.getElementById('footer_input').addEventListener(
  'blur',
  event => {
    // css変数操作
    document.documentElement.style.setProperty('--footer-height', '50px');

    if (isAndroid()) {
      document.getElementById('sumaho').style.display = 'none';
      window.scroll(0, scrollTop);
    }
  },
  true
);

// 初回
const init = setInterval(() => {
  move_bottom();
  clearInterval(init);
}, 250);



img/icon0.png




画像をダウンロード


img/send.png




画像をダウンロード


ホームへ