大将がゆく

写真を撮ったり、イラストを描いたりするバイクで日本一周した主夫の日記帳

【コピペOK】画像比較スライダーを はてなブログ に設置しました

f:id:taisho_goes:20191224184156j:plain

1. やりたかったこと

ブログに写真を掲載していると、レタッチ前後の画像の比較などわかりやすく掲載したいことがあります。

そこで以下のような比較用のスライダーを はてなブログ に設置することにしました。

※中央のバーを左右に動かすと比較できます

ちなみに私のプログラミング経験はゼロ。

一応、15年ほど前、高校の授業でHTMLをやったのですが、まだCSSは "最新の技術" 扱いで教科書のコラムで触れた程度です。

今回の作業を通じてHTML、CSS、JavaScriptの三者の関係を知りました。

このブログのカスタマイズも基本的には先人がやった通りのコピペで乗り越えてきました。

そんなレベルです。

2. 必要なもの

今回の作業で必要だったものは以下の2点です。

  • パソコン
  • めげない気持ち

また、私の作業環境は以下のとおり。

  • Apple MacBook Pro
    • ブラウザ:Google Chrome 79
    • エディタ:テキストエディット
  • はてなブログPRO
    • テーマ:ZENO-TEAL
    • 編集モード:Markdown記法

3. 設定作業

3-0. 作業概要

今回、「Cocoen」という画像比較用スライダー(before-after slider)を設置します。

https://koenromers.com/cocoen/

選定理由は

  1. たまたま情報が手に入った
  2. 本文中に挿入するコードが少ないので、楽に運用ができそう

というものです。

なお、Google先生が提示してくれた他のブログにあった内容では、同じページ内で複数箇所にCocoenを設置するとスライダーがうまく機能しませんでした。

そのため上記の作者サイト中にある “Multiple Cocoens in one page:” を参考に、JavaScriptを一部変更しています。

素人工事なので、ちゃんとしたスキルやナレッジがある人からすれば『もっといい方法あるよ!』と思われるかもしれません。あしからず。

3-1. JavaScriptの編集

管理画面中の「デザイン → カスタマイズ → 記事 → 記事下」に以下のコードを貼付します。

上記の方法ではCocoenを用いないページやエントリーでもJavaScriptが読み込まれるため、ページ速度が低下してしまいます。

結局、以下の<script>〜</script>ブログ本文の末尾に直接記述することにしました。力技です。パワープレーです。

<!-- 画像比較スライダー -->
<script>
!function(e) {
    if ("object" == typeof exports && "undefined" != typeof module)
        module.exports = e();
    else if ("function" == typeof define && define.amd)
        define([], e);
    else {
        var t;
        t = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : this, t.Cocoen = e()
    }
}(function() {
    return function e(t, n, i) {
        function s(o, a) {
            if (!n[o]) {
                if (!t[o]) {
                    var l = "function" == typeof require && require;
                    if (!a && l)
                        return l(o, !0);
                    if (r)
                        return r(o, !0);
                    var d = new Error("Cannot find module '" + o + "'");
                    throw d.code = "MODULE_NOT_FOUND", d
                }
                var h = n[o] = {
                    exports: {}
                };
                t[o][0].call(h.exports, function(e) {
                    var n = t[o][1][e];
                    return s(n ? n : e)
                }, h, h.exports, e, t, n, i)
            }
            return n[o].exports
        }
        for (var r = "function" == typeof require && require, o = 0; o < i.length; o++)
            s(i[o]);
        return s
    }({
        1: [function(e, t, n) {
            "use strict";
            function i(e, t) {
                if (!(e instanceof t))
                    throw new TypeError("Cannot call a class as a function")
            }
            var s = Object.assign || function(e) {
                    for (var t = 1; t < arguments.length; t++) {
                        var n = arguments[t];
                        for (var i in n)
                            Object.prototype.hasOwnProperty.call(n, i) && (e[i] = n[i])
                    }
                    return e
                },
                r = function() {
                    function e(e, t) {
                        for (var n = 0; n < t.length; n++) {
                            var i = t[n];
                            i.enumerable = i.enumerable || !1, i.configurable = !0, "value" in i && (i.writable = !0), Object.defineProperty(e, i.key, i)
                        }
                    }
                    return function(t, n, i) {
                        return n && e(t.prototype, n), i && e(t, i), t
                    }
                }(),
                o = function() {
                    function e(t, n) {
                        i(this, e), this.options = s({}, e.defaults, n), this.element = t || document.querySelector(".cocoen"), this.init()
                    }
                    return r(e, [{
                        key: "init",
                        value: function() {
                            this.createElements(), this.addEventListeners(), this.dimensions()
                        }
                    }, {
                        key: "createElements",
                        value: function() {
                            var e = document.createElement("span");
                            e.className = this.options.dragElementSelector.replace(".", ""), this.element.appendChild(e);
                            var t = document.createElement("div"),
                                n = this.element.querySelector("img:first-child");
                            t.appendChild(n.cloneNode(!0)), n.parentNode.replaceChild(t, n), this.dragElement = this.element.querySelector(this.options.dragElementSelector), this.beforeElement = this.element.querySelector("div:first-child"), this.beforeImage = this.beforeElement.querySelector("img")
                        }
                    }, {
                        key: "addEventListeners",
                        value: function() {
                            this.element.addEventListener("click", this.onTap.bind(this)), this.element.addEventListener("mousemove", this.onDrag.bind(this)), this.element.addEventListener("touchmove", this.onDrag.bind(this)), this.dragElement.addEventListener("mousedown", this.onDragStart.bind(this)), this.dragElement.addEventListener("touchstart", this.onDragStart.bind(this)), window.addEventListener("mouseup", this.onDragEnd.bind(this)), window.addEventListener("resize", this.dimensions.bind(this))
                        }
                    }, {
                        key: "dimensions",
                        value: function() {
                            this.elementWidth = parseInt(window.getComputedStyle(this.element).width, 10), this.elementOffsetLeft = this.element.getBoundingClientRect().left + document.body.scrollLeft, this.beforeImage.style.width = this.elementWidth + "px", this.dragElementWidth = parseInt(window.getComputedStyle(this.dragElement).width, 10), this.minLeftPos = this.elementOffsetLeft + 10, this.maxLeftPos = this.elementOffsetLeft + this.elementWidth - this.dragElementWidth - 10
                        }
                    }, {
                        key: "onTap",
                        value: function(e) {
                            e.preventDefault(), this.leftPos = e.pageX ? e.pageX : e.touches[0].pageX, this.requestDrag()
                        }
                    }, {
                        key: "onDragStart",
                        value: function(e) {
                            e.preventDefault();
                            var t = e.pageX ? e.pageX : e.touches[0].pageX,
                                n = this.dragElement.getBoundingClientRect().left + document.body.scrollLeft;
                            this.posX = n + this.dragElementWidth - t, this.isDragging = !0
                        }
                    }, {
                        key: "onDragEnd",
                        value: function(e) {
                            e.preventDefault(), this.isDragging = !1
                        }
                    }, {
                        key: "onDrag",
                        value: function(e) {
                            e.preventDefault(), this.isDragging && (this.moveX = e.pageX ? e.pageX : e.touches[0].pageX, this.leftPos = this.moveX + this.posX - this.dragElementWidth, this.requestDrag())
                        }
                    }, {
                        key: "drag",
                        value: function() {
                            this.leftPos < this.minLeftPos ? this.leftPos = this.minLeftPos : this.leftPos > this.maxLeftPos && (this.leftPos = this.maxLeftPos);
                            var e = this.leftPos + this.dragElementWidth / 2 - this.elementOffsetLeft;
                            e /= this.elementWidth;
                            var t = 100 * e + "%";
                            this.dragElement.style.left = t, this.beforeElement.style.width = t, this.options.dragCallback && this.options.dragCallback(e)
                        }
                    }, {
                        key: "requestDrag",
                        value: function() {
                            window.requestAnimationFrame(this.drag.bind(this))
                        }
                    }]), e
                }();
            o.defaults = {
                dragElementSelector: ".cocoen-drag",
                dragCallback: null
            }, t.exports = o
        }, {}]
    }, {}, [1])(1)
});
</script>
<script>
    document.querySelectorAll('.cocoen').forEach(function(element){
    new Cocoen(element);
    });
</script>

今回は「記事下」内の先頭にコピペしました。

なんとなく『シェアボタンやスターより先に読み込んだほうがいいのかな?』と思ったからです。

本来は.js形式の別ファイルとしてアップロードして、読み込んだ方がスマートだと思います。

でも、はてなブログではそんなことができないので、力技で乗り切ります。

3-2. CSSの編集

管理画面中の「デザイン → カスタマイズ → デザインCSS」に以下のコードを貼付します。

/* 画像比較スライダー */
.cocoen {
    box-sizing: border-box;
    cursor: pointer;
    line-height: 0;
    margin: 0;
    overflow: hidden;
    padding: 0;
    position: relative;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select:none
}

.cocoen * {
    box-sizing:inherit
}

.cocoen ::after, .cocoen ::before {
    box-sizing:inherit
}

.cocoen img, .cocoen picture > img {
    max-width:none
}

.cocoen > img, .cocoen > picture > img {
    display: block;
    width:100%
}

.cocoen > div:first-child, picture .cocoen > div {
    height: 100%;
    left: 0;
    overflow: hidden;
    position: absolute;
    top: 0;
    width:50%
}

.cocoen-drag {
    background: #fff;
    bottom: 0;
    cursor: ew-resize;
    left: 50%;
    margin-left: -1px;
    position: absolute;
    top: 0;
    width:2px
}

.cocoen-drag::before {
    border: 3px solid #fff;
    content: '';
    height: 30px;
    left: 50%;
    margin-left: -7px;
    margin-top: -18px;
    position: absolute;
    top: 50%;
    width: 14px
}

「デザインCSS」のところでは、もともとテーマに関することが最初から書かれています。

このデフォルトの記述より前にカスタムを入れると動作不良になることがあるようなので、コメントアウトの後にコピペしました。

ここでも貼り付けをしたら、忘れないように「変更を保存」をクリックします。

4. 使い方

使い方は非常に簡単です。

画像を貼り付ける際に以下のHTMLコードを本文中に使用するだけで比較用スライダーが使えます。

<p>
    <div class="cocoen">
        <img src="左側の画像URL">
        <img src="右側の画像URL">
    </div>
</p>

4-1. 画像URLの取得方法

<img src="画像URL">のところには画像のアドレスを挿入する必要があります。

そのアドレスはブログ編集画面ではなく、はてなフォトライフから取得する必要があります。

  1. はてなフォトライフ に左右の画像をアップロード(編集画面からアップロードでもOK)
  2. はてなフォトライフ で画像を表示する
  3. 画像上で右クリック画像アドレスをコピー(macOSの場合)

4-2. キャプション

比較スライダーに説明書きを添えたい場合には、以下のコードを末尾の</p>の直前に入れます。

<figcaption class="figure-image figure-image-fotolife">
    画像キャプション
</figcaption>

こうすれば普通に写真を貼り付けたときと似たようなスタイルでキャプションが表示されました。

4-3. サンプル

この記事の冒頭にも設置したこのミカンの画像の比較部分は以下のようなコードを書いています。

※中央のバーを左右に動かすと比較できます

↓↓↓

<p>
    <div class="cocoen">
        <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taisho_goes/20191222/20191222120531.jpg">
        <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taisho_goes/20191222/20191222120535.jpg">
    </div>
    <figcaption class="figure-image figure-image-fotolife">
        ※中央のバーを左右に動かすと比較できます
    </figcaption>
</p>

5. 今後の課題

このスライダー設置が完了してから嬉々としてお嫁ちゃんにブログを読んでもらいました。

すると「……スライダー?どれ?」と言われました。

確かに、Cocoenのスライダーは矢印などが出ないので、画像上の白線が操作できることが読者に分かりにくいデザインです。

この点においてTwentyTwentyなど他のツールが優れていると考えています。

https://zurb.com/playground/twentytwenty

せっかく設置したCocoenのUIがカスタマイズ可能なのか、それとも別のツールを使用した方がいいのか。

とりあえず、しばらくはCocoenをこのまま利用しながら、引き続き研究を進めていこうと思います。

ではでは……。