みかづきブログ その3

3ヶ月つづけてみました。

CSSでメッセージアプリ風のUIを再現する

CSSでメッセージアプリ風のUIを再現してみました。
是非ともiPhoneでご観覧頂きたいです。


DEMO

See the Pen hello world. by kimmy (@kimmy) on CodePen.


HTML

<div id="global-wrapper">
  <header id="global-header">
    <h1 class="ttl">Message</h1>
  </header>

  <div id="global-stage">
    <div id="global-stage-inner" class="inner">
    </div>
  </div>

  <footer id="global-footer">
    <form id="global-footer-form" class="form">
      <input id="global-footer-form-txt" class="txt" type="text"></input>
    <input id="global-footer-form-btn" class="btn submit" type="submit" value="送信" />
    </form>
  </footer>
</div>

CSS

@charset "UTF-8";

section,
nav,
article,
aside,
hgroup,
header,
footer,
figure,
figcaption,
details,
summary,
main {
  display: block;
}

output {
  display: inline;
}

progress,
meter {
  display: inline-block;
}

datalist {
  display: none;
}

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font: inherit;
  font-size: 100%;
  vertical-align: baseline;
}

html {
  line-height: 1;
}

ol, ul {
  list-style: none;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

caption, th, td {
  text-align: left;
  font-weight: normal;
  vertical-align: middle;
}

q, blockquote {
  quotes: none;
}

q:before, q:after, blockquote:before, blockquote:after {
  content: "";
  content: none;
}

a img {
  border: none;
}

article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
  display: block;
}

html {
  height: 100%;
}

body {
  height: 100%;
  font: 16px "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", Meiryo, "メイリオ", sans-serif;
}

#global-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
}

#global-header {
  position: relative;
  width: 100%;
  height: 44px;
  text-align: center;
  background: -webkit-linear-gradient(#3e4758, #141821);
  background: linear-gradient(#3e4758, #141821);
  box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
  z-index: 10;
}

#global-header .ttl {
  color: #fff;
  font-weight: bold;
  letter-spacing: .2em;
  line-height: 44px;
}

#global-stage {
  position: absolute;
  top: 44px;
  bottom: 44px;
  left: 0;
  right: 0;
  background: #e4eef7;
  overflow: scroll;
  -webkit-overflow-scrolling: touch;
}

#global-footer {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 44px;
  background: -webkit-linear-gradient(#e4ebf1, #b1b5b8);
  background: linear-gradient(#e4ebf1, #b1b5b8);
  box-shadow: 0 -2px 2px rgba(0, 0, 0, 0.2);
  overflow: hidden;
  z-index: 10;
}

#global-footer .form {
  position: relative;
  margin: 0 auto;
  width: 320px;
}

#global-footer .txt {
  position: absolute;
  border-color: rgba(0, 0, 0, 0.2);
  top: 6px;
  left: 10px;
  border-radius: 9px;
  width: 200px;
  height: 24px;
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.2) inset;
  -webkit-appearance: none;
}

#global-footer .btn {
  position: absolute;
  padding: 0;
  border-color: rgba(0, 0, 0, 0.2);
  border-radius: 10px;
  top: 7px;
  right: 10px;
  width: 50px;
  height: 30px;
  color: #fff;
  font-size: 16px;
  font-weight: bold;
  text-align: center;
  background: -webkit-linear-gradient(#4f94dd, #2a5da1);
  background: linear-gradient(#4f94dd, #2a5da1);
  -webkit-appearance: none;
}

.msg {
  -webkit-transition: opacity .5s ease;
  transition: opacity .5s ease;
}

.msg .txt {
  display: inline-block;
  position: relative;
  margin: 20px;
  padding: 5px 10px;
  border-radius: 15px;
  background: -webkit-linear-gradient(#ffffff, #dbdfe2);
  background: linear-gradient(#ffffff, #dbdfe2);
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
}

.msg .txt:after {
  display: block;
  position: absolute;
  margin-top: -6px;
  top: 50%;
  left: -16px;
  border: solid 6px rgba(0, 0, 0, 0);
  border-right: solid 10px #b9b9b9;
  width: 0;
  height: 0;
  content: "";
}

.msg.mine {
  text-align: right;
}

.msg.mine .txt {
  background: -webkit-linear-gradient(#c7f377, #97d267);
  background: linear-gradient(#c7f377, #97d267);
}

.msg.mine .txt:after {
  top: 50%;
  left: auto;
  right: -16px;
  border: solid 6px rgba(0, 0, 0, 0);
  border-left: solid 10px #b9b9b9;
  width: 0;
  height: 0;
  content: "";
}

.msg.invisble {
  opacity: 0;
}

JavaScript

(function(win, doc) {

    "use strict";

    win.App = win.App || {};

})(this, document);
(function(win, doc, ns) {

    "use strict";

    function EventDispatcher() {
        this._events = {};
    }

    EventDispatcher.prototype.hasEventListener = function(eventName) {
        return !!this._events[eventName];
    };
   
    EventDispatcher.prototype.addEventListener = function(eventName, callback) {
        if (this.hasEventListener(eventName)) {
            var events = this._events[eventName],
                length = events.length,
                i = 0;

            for (; i < length; i++) {
                if (events[i] === callback) {
                    return;
                }
            }

            events.push(callback);
        } else {
            this._events[eventName] = [callback];
        }
    };

    EventDispatcher.prototype.removeEventListener = function(eventName, callback) {
        if (!this.hasEventListener(eventName)) {
            return;
        } else {
            var events = this._events[eventName],
                i = events.length,
                index;

            while (i--) {
                if (events[i] === callback) {
                    index = i;
                }
            }

            events.splice(index, 1);
        }
    };

    EventDispatcher.prototype.fireEvent = function(eventName, opt_this, opt_arg) {
        if (!this.hasEventListener(eventName)) {
            return;
        } else {
            var events     = this._events[eventName],
                copyEvents = _copyArray(events),
                arg        = _copyArray(arguments),
                length     = events.length,
                i = 0;

            // eventNameとopt_thisを削除
            arg.splice(0, 2);

            for (; i < length; i++) {
                copyEvents[i].apply(opt_this || this, arg);
            }
        }

        function _copyArray(array) {
            var newArray = [],
                i = 0;

            try {
                newArray = [].slice.call(array);
            } catch(e) {
                for (; i < array.length; i++) {
                    newArray.push(array[i]);
                }
            }

            return newArray;
        }
    };

    ns.EventDispatcher = EventDispatcher;

})(this, document, App);
(function(win, doc, $, ns) {

    "use strict";

    var instance, originalConstructor;

    function MassageList() {
        var that = this;

        _init();

        function _init() {
            ns.EventDispatcher.call(that);
        }

        this.him = [
            "hello world."
        ];
    }

    originalConstructor = MassageList.prototype.constructor;
    MassageList.prototype = new ns.EventDispatcher();
    MassageList.prototype.constructor = originalConstructor;
    originalConstructor = null;

    MassageList.getInstance = function() {
        if (!instance) {
            instance = new MassageList();
        }

        return instance;
    };

    ns.MassageList = MassageList;

})(this, document, $, App);
(function(win, doc, $, ns) {

    "use strict";

    var instance, originalConstructor;

    function MassageManager() {
        var massageList = ns.MassageList.getInstance();

        var that        = this,
            historyList = [],
            index = 0,
            LOOP_INDEX = 1;

        _init();

        function _init() {
            ns.EventDispatcher.call(that);
            receive();
        }

        function _handlePost(evt) {
            var interval = 500 + Math.random() * 1000 | 0;

            that.fireEvent("POST", evt, evt);

            if (evt.target === "mine") {
                setTimeout(function() {
                    receive();
                }, interval);
            }
        }

        function send(txt) {
            if (!txt) {
                return;
            }

            var msg = new ns.Massage(txt, true);

            msg.addEventListener("POST", _handlePost);
            historyList.push(msg);
        }

        function receive() {
            var msg = new ns.Massage(massageList.him[index], false);

            msg.addEventListener("POST", _handlePost);
            historyList.push(msg);

            if (!!massageList.him[index + 1]) {
                ++index;
            } else {
                index = massageList.him.length - LOOP_INDEX;
            }
        }

        this.send = send;
    }

    originalConstructor = MassageManager.prototype.constructor;
    MassageManager.prototype = new ns.EventDispatcher();
    MassageManager.prototype.constructor = originalConstructor;
    originalConstructor = null;

    MassageManager.getInstance = function() {
        if (!instance) {
            instance = new MassageManager();
        }

        return instance;
    };

    ns.MassageManager = MassageManager;

})(this, document, $, App);
(function(win, doc, $, ns) {

    "use strict";

    var $stage = $("#global-stage"),
        $inner = $stage.find("#global-stage-inner"),
        originalConstructor;

    function Massage(txt, isMine) {
        if (!txt) {
            return;
        }

        var that  = this,
            klass = isMine ? "invisble msg mine" : "invisble msg",
            $msg  = $('<div class="' + klass + '"><p class="txt">' + txt + '</p></div>');

        _init();

        function _init() {
            ns.EventDispatcher.call(that);

            $inner.append($msg);
            $stage.animate({scrollTop: $inner.height()}, 200);

            setTimeout(function() {
                $msg.removeClass("invisble");
                that.fireEvent("POST", that, that);
            }, 100);
        }

        that.txt    = txt;
        that.target = isMine ? "mine" : "him";
    }

    originalConstructor = Massage.prototype.constructor;
    Massage.prototype = new ns.EventDispatcher();
    Massage.prototype.constructor = originalConstructor;
    originalConstructor = null;

    ns.Massage = Massage;

})(this, document, $, App);
(function(win, doc, $, ns) {

    "use strict";

    var messageManager = ns.MassageManager.getInstance(),
        $form = $("#global-footer-form"),
        $txt  = $("#global-footer-form-txt");

    $form.on("submit", handleSubmit);

    function handleSubmit() {
        messageManager.send($txt.val(), true);

        $txt.val("");

        return false;
    }

})(this, document, $, App);

シャドウとかグラデーションがつかえるようになるとCSSが楽しくなりますね。
はやく対応してほしいものです。