脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

画像を重ねる方法(positionとかtopとか)

html5で画像を重ねて表示する方法
毎回力技でやっているので備忘録として
使用するpropertyは主にpositionとz-indexと位置に関するtop/left/bottom/right

基点の指定(position)

  • relative, absolute, static, fixedをとる
  • 指定しない場合のデフォルトはstatic

static

  • positionのデフォルト値
  • topとかleft等の位置情報やz-indexを指定しても反映されない
<div>
  <img src="freedom.jpg" />
</div>

f:id:portaltan:20170530123620p:plain

明示的にstaticを指定して位置情報を与えても変わらない

<div style="position: static; top: 150px; left: 100px;">
  <img src="freedom.jpg" />
</div>

f:id:portaltan:20170530123628p:plain

absolute

  • 親ボックスの端を基準にして、指定距離だけ移動した位置に表示
    • 親ボックスとはpositionプロパティにstatic以外が指定されている要素のこと
    • 何も指定されない場合はwindowの端が基準になる
<div style="position: absolute; top: 150px; left: 100px;">
  <img src="freedom.jpg" />
</div>

次にpositionをabsoluteに変更するとtopとleftプロパティが反映され、上端から150px、左端から100pxずれた位置に画像が表示される
f:id:portaltan:20170530123731p:plain

ここでずれた位置に画像を一つ追加してみる

<div>
  <img src="kira.png" />
</div>

<div style="position: absolute; top: 150px; left: 100px;">
  <img src="freedom.jpg" />
</div>

見事新しい画像が先ほどずらした150pxの空間に配置されました
f:id:portaltan:20170530124410p:plain

relative

  • 本来そのコンテンツが表示される位置を基準に指定した値だけ移動して表示する
<div style="position: relative; top: 100px; left: 100px;">
  <img src="freedom.jpg" />
</div>

単純に1つの画像を表示するだけではabsoluteとほぼ同じ
f:id:portaltan:20170530123731p:plain

次にabsoluteとの時と同じくずらした位置に画像を追加します

<div>
  <img src="kira.png" />
</div>

<div style="position: relative; top: 150px; left: 100px;">
  <img src="freedom.jpg" />
</div>

f:id:portaltan:20170530124532p:plain
すると今度は少し下の位置に表示されてしまいました
これはabsoluteの場合は親ボックス(今回はwindowの端)から指定した分だけ移動した位置に表示されていたのが、relativeの場合は実際に表示されるはずだった位置から指定した分だけ移動しているためです
f:id:portaltan:20170530125118p:plain

fixed

  • windowの端から指定した距離だけ移動
  • そしてスクロールしても移動せずに固定(fix)される

位置の指定(top / bottom/ left / right)

  • topは上記で指定した基準からどの程度下にずらすかを示す
  • bottom, left, rightは同様に下、左、右からどの程度ずらすかを示す
<div style="position: absolute; top: 50px;">
  <img src="freedom.jpg" />
</div>

f:id:portaltan:20170530130335p:plain

<div style="position: absolute; bottom: 50px;">
  <img src="freedom.jpg" />
</div>

f:id:portaltan:20170530130425p:plain

  • topとbottom, leftとright両方を指定したい場合、それぞれtopとleftが優先される
<div style="position: absolute; top: 50px; bottom: 50px; left: 50px; right: 50px;">
  <img src="freedom.jpg" />
</div>

f:id:portaltan:20170530130537p:plain

画像の重ね方

やっと本題の画像の重ね方ですが、上のpositionの位置の指定を使えば簡単にできます
今回はフリーダムの画像の上にキラの写真を重ねます

absoluteの場合

<div>
  <img src="freedom.jpg" />
</div>

<div style="position: absolute; top: 100px; left: 100px;">
  <img src="kira.png" />
</div>

f:id:portaltan:20170530131143p:plain
親ボックスの左上を基点にして下に100px、右に100px移動したところにキラの顔が表示されました

relativeの場合

上記のpositionを単純にrelativeにすると以下のようになります
f:id:portaltan:20170530131558p:plain
これは上で説明したように基点が実際に表示される場所(フリーダムの画像の下)になるためです
このケースでもし画像を重ねあわせたい場合は位置を指定する際に逆にマイナスを指定することで重ねることができます
f:id:portaltan:20170530154251p:plain

更に画像を重ねる場合

同じようにpositionと位置を指定していけば何重にも画像を重ねることができます

<div>
  <img src="freedom.jpg" />
</div>

<div style="position: absolute; top: 100px; left: 100px;">
  <img src="kira.png" />
</div>

<div style="position: absolute; top: 150px; left: 150px;">
  <img src="kira2.jpg" />
</div>

f:id:portaltan:20170530132656p:plain
f:id:portaltan:20170530134553p:plain

重なりの順番(z-index)

今回は順番的に上から『キラ⇒キラ⇒キラの乗ってたやつ』となりましたが、この順番もz-indexで変更することができます

  • z-indexの値の大きい要素が上に表示されます
  • デフォルトは親要素と同じ値か0です
  • positionがstaticの場合は反映されない

なので以下のように記述すれば一番下のフリーダムが1番上に表示されて、他の2枚はその下に隠れます

<div style="position: absolute; z-index: 20;">
  <img src="freedom.jpg" />
</div>

<div style="position: absolute; top: 100px; left: 100px;">
  <img src="kira.png" />
</div>

<div style="position: absolute; top: 150px; left: 150px;">
  <img src="kira2.jpg" />
</div>

f:id:portaltan:20170530133735p:plain

absoluteとrelativeの違い(親要素の高さ)

relativeとabsoluteの違いとしてもう1つあげられるのが、親要素の高さの扱いです。

  • relativeの場合はそのまま残る
  • absoluteの場合はなくなる

両方staticの場合、親要素に色をつけて見やすくしてみると

<div style="background-color: blue;">
  <img src="freedom.jpg" />
</div>
<div style="background-color: red;">
  <img src="justice.jpg" />
</div>

f:id:portaltan:20170530155700p:plain

ここから上の要素をrelative、下をabsoluteにすると

<div style="position: relative; background-color: blue;">
  <img src="freedom.jpg" />
</div>
<div style="position: absolute; background-color: red;">
  <img src="justice.jpg" />
</div>

f:id:portaltan:20170530155821p:plain

逆にすると上の要素の高さがなくなり、下のrelativeの要素が基点とする位置がずれて両方の画像が重なってしまいます
わかりやすくabsoluteの方を上になるようにz-indexで調整しています

<div style="position: absolute; background-color: blue; z-index: 2;">
  <img src="freedom.jpg" />
</div>
<div style="position: relative; background-color: red;">
  <img src="justice.jpg" />
</div>

f:id:portaltan:20170530160219p:plain

Bootstrapのカルーセル機能の使用方法

carousel

複数の画像が一定間隔でスライドしていく機能のことです
詳しくは下記のリンクをご参照ください。
http://getbootstrap.com/2.3.2/javascript.html#carousel

使い方

<div id="test-carousel" class="carousel slide" data-ride="carousel" data-interval="3000">

  <!-- 何枚目かの画像かを表すインディケーター -->
  <ol class="carousel-indicators">
    <li data-target="#test-carousel" data-slide-to="0" class="active"></li>
    <li data-target="#test-carousel" data-slide-to="1" class=""></li>
    <li data-target="#test-carousel" data-slide-to="2" class=""></li>
  </ol>

  <!-- 実際の画像の表示処理 -->
  <div class="carousel-inner">
    <div class="item active">    <!-- activeをつけたものがページ表示時に表示される -->
      <img src="./picture1.jpg">
    </div>
    <div class="item">
      <img src="./picture2.jpg">
    </div>
    <div class="item">
      <img src="./picture3.jpg">
    </div>
  </div>

  <!-- 前の画像へ戻るための矢印ポインタ -->
  <a class="left carousel-control" href="#test-carousel" data-slide="prev">
    <span class="glyphicon glyphicon-chevron-left"></span>
  </a>

  <!-- 次の画像へ進むための矢印ポインタ -->
  <a class="right carousel-control" href="#test-carousel" data-slide="next">
    <span class="glyphicon glyphicon-chevron-right"></span>
  </a>
</div>
data-interval

1行目のdata-intervalが何秒毎に画像をスライドさせるかという設定です。
この場合は3秒です。

bootstrapとjqueryでタブ表示を作成する方法

必要なもの

手順

まずはTabのheaderを作成します。
<!-- Nav tabs -->
<div id="tabs">
  <!-- Tabs header -->
  <ul>
    <li><a id="tabs1" href="#content1">Tab1</a></li>
    <li><a id="tabs2" href="#content2">Tab2</a></li>
  </ul>
</div>

するとこんな感じのtabが出来ます
f:id:portaltan:20150929171825p:plain

次にタブの中のコンテンツを作成します。
<!-- Nav tabs -->
<div id="tabs">

  <!-- Tabs header -->
  <ul>
    <li><a id="tabs1" href="#content1">Tab1</a></li>
    <li><a id="tabs2" href="#content2">Tab2</a></li>
  </ul>

  <!-- Tabs body -->
  <div id="content1" class="panel">
    content1
  </div>
  <div id="content2" class="panel">
    content2
  </div>
</div>

こうするとコンテンツ内の内容が表示されるようになります。
しかしこの段階では全てのコンテンツが全てのタブに表示されています。
f:id:portaltan:20150929172108p:plain

次にjs側の設定を行います。
$('[id^=tabs]').click(function(){
    $("#tabs li").removeClass("active");
    $(this).parent().addClass("active");
    $("#tabs .panel").hide();
    $(this.hash).fadeIn();
    return false;
});

これでクリックすると各タブに対応した(hrefで指定した)コンテンツが表示されます。
f:id:portaltan:20150929173202p:plain

ページ表示時の設定

しかしこの段階ではクリックする前のページ表示時は全コンテンツが表示されてしまいます。
ページ表示時
f:id:portaltan:20150929173537p:plain

なので、最初に表示させるタブを設定します。

$("#tabs1:eq(0)").trigger('click');
  • 上記ではtabs1をデフォルトで開くタブに設定しています。

これでページ表示時にもちゃんと一つのタブの内容のみが表示されます
ページ表示時
f:id:portaltan:20150929173631p:plain

tablesoterをRailsで使う方法 + FilterとSort機能の使い方

jueryプラグインのtablesorterをRailsで使う方法です。

f:id:portaltan:20150916151141p:plain

tablesorterはtableをsortしたりfilteringするjQueryプラグインです。

ソースコード

まずはじめに、tablesorterには本家とforkして他の人が開発しているPageの二つのドキュメントがあります
本家:
http://tablesorter.com/docs/

Fork:
https://mottie.github.io/tablesorter/docs/

今回はForkの方を利用する方法を記載します。

ライブラリのダウンロード

app/assets/javascriptsディレクトリに

をダウンロードして配置します
https://mottie.github.io/tablesorter/docs/#Download


tablesorterの処理を記述

tableの作成

まずは普通にhtmlでtableを作成します。

<table class="tablesorter" border="1">
  <thead>
    <tr>
      <th>column1</th>
      <th>column2</th>
      <th>column3</th>
    </tr>
  </thead>
  <tbody class="table-contents">
    <tr>
      <td>1</td>
      <td>3</td>
      <td>2</td>
    </tr>
    <tr>
      <td>foo</td>
      <td>baz</td>
      <td>bar</td>
    </tr>
  </tbody>
</table>
  • class属性にtablesorterを指定します

この時点ではこんな感じのtableです。
f:id:portaltan:20150916142953p:plain

jsの設定(tablesorter_test.js)
$('table.tablesorter').tablesorter({ 
});
  • 名前はなんでもいいです

application.jsの編集

app/assets/javascripts/application.jsに以下を記述します

//= require jquery-2.1.1.min
//= require jquery.tablesorter.combined.js
//= require tablesorter_test.js
  • 順番は逆にしないようにしましょう
  • javascriptsディレクトリ以下全てを読み込む設定にしている場合はそのままでOKです

これだけで一番単純なsort機能は利用できるようになりました
f:id:portaltan:20150916143135p:plain

  • 各項目でsortすることが出来ます

TableSorterの他の機能の利用

まずデフォルトのTableがこのような状態です
f:id:portaltan:20150916143451p:plain

デフォルトでsortさせる

上のままではPage読み込み時にはsortは行われていません。
これを最初から特定のcolumnでsortしておくことが可能です。

sortList: [[${何列目でsortするか}, ${昇順(0)か降順(1)か}]]
  • 一番左の列を0とします

例1

$('table.tablesorter').tablesorter({
    sortList: [[0,0]],
});

f:id:portaltan:20150916143818p:plain

  • column1(0)で昇順(0)でsortされています

例2

$('table.tablesorter').tablesorter({
    sortList: [[2,1]],
});

f:id:portaltan:20150916144212p:plain

  • column3(2)で降順(1)でsortされています

Filter機能

上でjquery.tablesorter.combined.jsを使用している場合は、defaultでfilter機能も利用できます

widgets: ["filter"]

例1

$('table.tablesorter').tablesorter({
    widgets: ["filter"],
})

f:id:portaltan:20150916145202p:plain

  • ちょっとずれていますが、filterボックスが各columnに表示され、入力された値でfilteringされうようになりました

filter機能を限定することも可能です

widgets: ["filter"],
headers: {${何個目の列か}: { filter: false }},

例2

$('table.tablesorter').tablesorter({
    widgets: ["filter"],
    headers: {0: { filter: false }, 2: { filter: false }},
});

column1(0)とcolumn3(2)のfilter機能は廃止(false)されています
f:id:portaltan:20150916145913p:plain

リセットボタン

filterに入力した値をリセットするボタンも簡単に作れます

まずはボタンを作ります

<button class="reset-filter-button">Reset Filter</button>

このボタンにリセット機能をセットします

widgetOptions : {
    filter_reset : 'button.reset-filter-button',
}

例1

$('table.tablesorter').tablesorter({
    widgets: ["filter"],
    widgetOptions : {
        filter_reset : 'button.reset-filter-button',
    }
});

これで入力値をリセットすることができます。
f:id:portaltan:20150916150609p:plain

f:id:portaltan:20150916150515p:plain

Reactでinputタグのautofocusを使うには

html

HTML5ではinputタグの中にautofocusと書けば、Pageを表示した際に自動でフォーカスを合わせてくれます。
<input autofocus>-HTML5タグリファレンス

React

しかし、Reactでは下のように普通にinputタグ内にautofocusと書いても反映されません。

var Test = React.createClass(
    {
        // 処理とか
    },
    render: function() {
        return (
            <input type='text' autofocus />
        };
    }
); 

以下のように書けばReactでもオートフォーカスされます。

.
.
    <input type='text' autoFocus={focus} />
.
.

Reactのstate(prop)内で改行文字を使いたい場合

JSX構文

ReactはJSXを採用しているので、構文の中で以下のようにhtmlタグを直接書くようににコーディングできます。

var Test = React.createClass(
    {
        // 処理とか
    },
    render: function() { 
        return(
            <div>
              {this.state.foobar}
            </div>
        );
    }
);

しかし、このhtmlタグライクなものは、実際のhtmlタグ(DOMノード)ではなく、React側で用意したコンパイラになります。
この<div>は、React.createElement(div)を呼び出しています。
以上のことより、ReactはHTML文字列を生成していないため、XSS対策の必要がありません。

問題

しかし同時に、HTML文字列を直接生成していないということは自分の意図したタグ(改行とか)を入れられないということになります。
<br>に関しては、<br />を使えば改行できますが、8行目のようなReactが管理している値の中に<br>や"\r\n"のような改行文字があっても、それらは単なるStringとして表されるので改行されることはありません。

ここで改行したい<br>さらにここでも改行したい<br>これが最終行

↑こんな風に無改行で表示されてしまします。

対策

ここで改行したい
さらにここでも改行したい
これが最終行

こんな風にするには一工夫が必要です。

var Test = React.createClass(
    {
        this.setState({foobar: 'ここで改行したい<br>さらにここでも改行したい<br>これが最終行'});
    },
    render: function() { 
        var lines = this.state.foobar.split('<br>').map(function(line) {
            return (<p>{line}</p>);
        });
        return(
            <div>
              {lines}
            </div>
        );
    }
);
  1. まずstateに入っている文字列を改行文字(<br>や\r\n)で分割します
  2. その後に、mapを使ってそれぞれを<p>タグで囲みます
  3. 実際のreturn内ではlinesを呼び出すだけにします

これで<p>タグによって改行される文字列が完成です。
実際は以下のようになります

<p>ここで改行したい</p>
<p>さらにここでも改行したい</p>
<p>これが最終行</p>

このままだと行間がすごいあいてしまうので、p タグにクラス属性をつけて、cssを設定してやれば行間もなくなります。
React

.
.
            return (<p className='res-line'>{line}</p>);
.
.

css

p.res-line {
    margin: 0;
}

※classNameに関しては以下を参照してください
http://portaltan.hatenablog.com/ReactのJSX構文内でclass属性を設定する場合 - 脳汁portal


この問題は色んな人がひっかかってるようで、調べると日本語でも色んなブログが出てきます。
Reactはこういう細かいところの融通がまだきかない気がするので、今後に期待したいです。


(2015/08/11追記)

ちょっと改良

このままだと一つのstateにしか対応してないので、表示するstate毎に処理を書かなければいけない。
ので改良した。

var Test = React.createClass(
    {
        getInitialState() {
            return {
                foo: '',
                bar: ''
            };

     },
     .
     .
     .
     render: function() {
         function lines(line) {
             if (line) {
                 return (<p className='no-margin'>{line}</p>);
             } else {
                 return (<p className='no-margin'>&nbsp;</p>);
             }
         }
         return (
             <div>
               {this.state.foo.split('<br>').map(lines)}
               {this.state.bar.split('<br>').map(lines)}
             </div>
         );
     }
変更点
  • まずpタグを作成しているところのメソッドを切り出した
  • mapのcallbackfuncとして上記のメソッドを指定する
  • mapに渡す<br>入りの文字列はstateから取得する
  • initialize時にundefined なのでsplitできないと怒られないようにInitialStateを設定しておく
  • 空白文字を入れることで、'<br><br>'などの複数行改行にも対応した

これで

this.state.${state名}.split(${改行文字}).map(lines)

でどのstateでも改行して表示させることが可能になりました。