boostrapとjqueryの小ネタ集【アニメーションなども】

##Boostrap系
###ヘルプテキスト

テキストフィールドなどのinput要素などに注意書きとして利用できる。

>Bootstrap 3 ではヘルプ表示に `.help-block` を使用していましたが、Bootstrap 4 からは `.form-text` を使用することになりました。

```
<label for="passwd8">Password</label>
<input type="password" id="passwd8" class="form-control" aria-describedby="help8">
<span id="help8" class="form-text text-muted">
  パスワードは8文字以上で入力してください。
</span>
```

##丸いボタンを作る
クラスに`rounded-pill`をつける。

```
btn btn-outline-secondary btn-sm rounded-pill
```

###ナビゲーションで使うドロップボックス

```
                    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                        <a class="dropdown-item" href="#">Action</a>
                        <a class="dropdown-item" href="#">Another action</a>
                        <div class="dropdown-divider"></div>
                        <a class="dropdown-item" href="#">Something else here</a>
                    </div>
```

##JavaScript系

###クリックされた要素を取得

jqueryの場合は`$(this)`とすれば良いだけ、簡単。

```JavaScript
    $('button').on('click', function () {
        console.log($(this));
    });
```

###checkboxのクリック時にチェックをつけて、他の処理も実行する

jqueryでやる場合は`click`ではなくて、`change`を使わないと上手くできない。
またcheckedを要素に追加したい時は`attr()`ではなく`checked()`を使う方が良き。（らしい。trueはHTMLではなく、JavaScriptだから）

```JavaScript
$(function () {
    $('.rating_star_js').on('change', function (e) {
        $(this).prop('checked',true);
       //他の処理
    });
});
```

###リアルタイムでテキストエリア内の文字数を表示

```HTMl
[html]
<p class="total_character_number">総文字数：<span id="total_character_number_js">{{$episode->character_number}}</span></p>
```

```JavaScript
[JavaScript]
$("#episode_textarea_js").on("keyup", function () {
        character_number = $(this).val().length;
        $('#total_character_number_js').text(character_number);
    });
```

###data属性でキャメルケースを使うと小文字に変換される。

```html:test.blade.php
<input type="test" data-userId="{{$user->id}}" id="test">
```

例えば上記のようなのがあったとすると、

```JavaScript:test.js
var test = document.querySelector("#test");
console.log(test.dataset.userid);
```

上記のように`userId`ではなく`userid`になる。

###特定の文字を差し引いた文字数を表示
htmlは上と同じ。

```JavaScript
$("#episode_textarea_js").on("keyup", function () {
        text =  $(this).val();
        character_number = text.length;

        //正規表現。今回は「《・|・｜・》」の4つの記号を指定。
        ex = /《|\||｜|》/g; 
        ex_number = 0;
        if (text.match(ex)) {
            ex_number = text.match(ex).length;
        }
        total_character_number = character_number - ex_number;
        $('#total_character_number_js').text(total_character_number);
    });

```

###リロード時にダイアログを表示する（変更内容が保存されない可能性があります。）

下記を表示したいviewファイルに個別で入力しておけばOK。

```JavaScript
<script>
    window.addEventListener('beforeunload', (event) => {
    event.preventDefault();
    event.returnValue = '';
    });
</script> 
```

###laravelでファイルを分けてJavaScriptを使う時

`window.変数名`とかにして、グローバルにする。

```Javascript
[html]
<button onclick="test();" class="btn btn-primary">テスト</button>

[js]
window.test = function kanaReading() {
    ~省略~
}
```

###プレビュー機能で改行を表示する

```Javascript
preview_text = $('#episode_textarea_js').val();
preview_text = preview_text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/`/g, '&#x60;').replace(/\r?\n/g,'<br />');
$("#content_preview_article_js").html(preview_text);
```

下記だとhtmlエスケープされないので、注意。
```Javascript
preview_text = preview_text.replace(/r?n/g, '<br/>');
```

参考：[改行コードをbrタグにして反映したいがhtmlエスケープもしたい時](https://hack-le.com/n2br/)

###選択範囲を指定した文字で囲む

今回の場合はルビ（読み仮名）機能などが必要だったので、その時に使う機能として必要でした。

```
[html]
//下記のボタンが重要
<button onclick="addText4();" class="btn btn-primary">ルビ（読み仮名）</button>
        {{ Form::model($episode) }}

        {{ Form::textarea('content', null, ['id'=>'textarea4','class'=>'novel_content_textarea mt-3','row'=>'20','placeholder'=>'エピソードの本文を入力してください。']) }}

        {{ Form::close() }}

[js]
window.addText4 = function addText4() {
    //テキストエリアと挿入する文字列を取得
    var area = document.getElementById('textarea4');
    var text1 = '('
    var text2 = ')《ここにルビ》'
    //カーソルの開始位置と終了位置を基準に分割
    area.value = area.value.substr(0, area.selectionStart) +
        text1 +
        area.value.substr(area.selectionStart, area.selectionEnd - area.selectionStart) +
        text2 +
        area.value.substr(area.selectionEnd);
}

```

##アニメーション

###スイッチボタン

```
[html]
<div id="switchArea">
            <input type="checkbox" id="switch1">
            <label for="switch1" id="label_switch"><span></span></label>
            <label for="switch1" id="swImg"></label>
        </div>


[css]
/* === ボタンを表示するエリア ============================== */
#switchArea {
    position: relative;
    margin: 0;
    width: 102px;
    background: $back_blue;
    border-radius: 17px;

    /* === チェックボックス ==================================== */
    input[type="checkbox"] {
        display: none;

        /* === チェックボックスのラベル（ONのとき） ================ */
        &:checked+#label_switch {
            border-color: #78bd78;
            /* 選択タブの枠線     */
        }

        /* === 丸部分のSTYLE（ONのとき） =========================== */
        &:checked~#swImg {
            transform: translateX(68px);
            background: #78bd78;
            border-color: #78bd78;
            ;
        }
    }

    /* === チェックボックスのラベル（標準） ==================== */
    label {
        display: block;
        box-sizing: border-box;
        height: 34px;
        border: 2px solid #999999;
        border-radius: 17px;
    }

    /* === 丸部分のSTYLE（標準） =============================== */
    #swImg {
        position: absolute;
        width: 26px;
        height: 26px;
        background: #999999;
        top: 4px;
        left: 4px;
        border-radius: 13px;
        transition: .2s;
    }
}

```

###radioボタンで使ったアニメーション

```
[html]
<div class="genre-radio form-group">
                <div class="box">
                    <input type="radio" id="genre1" name="genre">
                    <label for="genre1">異世界ファンタジー</label>
                    <input type="radio" id="genre2" name="genre">
                    <label for="genre2">現代ファンタジー</label>
                    <input type="radio" id="genre3" name="genre">
                    <label for="genre3">ウサギ</label>
                    <input type="radio" id="genre4" name="genre">
                    <label for="genre4">トリ</label>
                </div>
            </div>


[sass]

.genre-radio {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

.genre-radio:before,
.genre-radio:after {
    -webkit-box-sizing: inherit;
    box-sizing: inherit;
}

.genre-radio .box {
    width: 100%;
    margin: 2em auto;
    text-align: left;
    border: 1px solid white;
    border-radius: 3px;
    background: #ffffff;
}

.genre-radio input[type=radio] {
    display: none;
}

.genre-radio label:focus,
.genre-radio label:hover,
.genre-radio label:active,
.genre-radio input:checked+label {
    color: #da3c41;
}

.genre-radio label:focus:before,
.genre-radio label:hover:before,
.genre-radio label:active:before,
.genre-radio input:checked+label:before {
    border-color: #da3c41;
    background: #ffffff;
}

.genre-radio label {
    font-size: 1em;
    font-weight: bold;
    line-height: 1;
    position: relative;
    display: block;
    overflow: hidden;
    padding: 1em 1em 1em 3em;
    cursor: pointer;
    -webkit-transition: all 0.15s ease;
    transition: all 0.15s ease;
    white-space: nowrap;
    text-overflow: ellipsis;
    background: #ffffff;
}

.genre-radio label:before {
    position: absolute;
    top: 1em;
    left: 1em;
    width: 10px;
    height: 10px;
    content: '';
    border: 0.2em solid #cccccc;
    border-radius: 50%;
}

.genre-radio input:checked+label:before {
    border-color: #da3c41;
    background: #da3c41;
}

.genre-radio input:disabled+label {
    cursor: not-allowed;
    color: rgba(0, 0, 0, 0.5);
    background: #efefef;
}

.genre-radio input:disabled+label:hover {
    border-color: rgba(0, 0, 0, 0.1);
}

.genre-radio input:disabled+label:before {
    border-color: #ffffff;
    background: #ffffff;
}

```

##アコーディオン

![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560864/f551c1bc-b9b7-46d8-9ff8-aaae3aac29d8.gif)

```
[html]
<div class="section s_01">

                <div class="accordion_one">
                    <div class="accordion_header">オプション設定（任意）<div class="i_box"><i class="one_i"></i></div>
                    </div>
                    <div class="accordion_inner">
                        <div class="box_one">
                            <div class="txt_a_ac">
                                <div class="genre-radio-wrapper box-border">
                                    //ここに開いた時に表示したい内容
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

[sass]
// アコーディオン
.s_01 .accordion_one {
    max-width: 1200px;
    margin: 0 auto;
}

.s_01 .accordion_one .accordion_header {
    background-color: $border_color;
    color: #fff;
    font-size: 26px;
    font-weight: bold;
    padding: 20px 11%;
    text-align: center;
    position: relative;
    z-index: +1;
    cursor: pointer;
    transition-duration: 0.2s;
}

.s_01 .accordion_one .accordion_header:hover {
    opacity: .8;
}

.s_01 .accordion_one .accordion_header .i_box {
    display: flex;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 50%;
    right: 5%;
    width: 40px;
    height: 40px;
    border: 1px solid #fff;
    margin-top: -20px;
    box-sizing: border-box;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
    transform-origin: center center;
    transition-duration: 0.2s;
}

.s_01 .accordion_one .accordion_header .i_box .one_i {
    display: block;
    width: 18px;
    height: 18px;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
    transform-origin: center center;
    transition-duration: 0.2s;
    position: relative;
}

.s_01 .accordion_one .accordion_header.open .i_box {
    -webkit-transform: rotate(-360deg);
    transform: rotate(-360deg);
}

.s_01 .accordion_one .accordion_header .i_box .one_i:before,
.s_01 .accordion_one .accordion_header .i_box .one_i:after {
    display: flex;
    content: '';
    background-color: #fff;
    border-radius: 10px;
    width: 18px;
    height: 4px;
    position: absolute;
    top: 7px;
    left: 0;
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
    transform-origin: center center;
}

.s_01 .accordion_one .accordion_header .i_box .one_i:before {
    width: 4px;
    height: 18px;
    top: 0;
    left: 7px;
}

.s_01 .accordion_one .accordion_header.open .i_box .one_i:before {
    content: none;
}

.s_01 .accordion_one .accordion_header.open .i_box .one_i:after {
    -webkit-transform: rotate(-45deg);
    transform: rotate(-45deg);
}

.s_01 .accordion_one .accordion_inner {
    display: none;
    padding: 30px 30px;
    border-left: 1px solid $border_color;
    border-right: 1px solid $border_color;
    border-bottom: 1px solid $border_color;
    box-sizing: border-box;
}

@media screen and (max-width: 1024px) {
    .s_01 .accordion_one .accordion_header {
        font-size: 18px;
    }

    .s_01 .accordion_one .accordion_header .i_box {
        width: 30px;
        height: 30px;
        margin-top: -15px;
    }
}

@media screen and (max-width: 767px) {
    .s_01 .accordion_one .accordion_header {
        font-size: 16px;
        text-align: left;
        padding: 15px 60px 15px 15px;
    }
}


[js]
// .s_01 .accordion_one
$(function () {
    //.accordion_oneの中の.accordion_headerがクリックされたら
    $('.s_01 .accordion_one .accordion_header').click(function () {
        //クリックされた.accordion_oneの中の.accordion_headerに隣接する.accordion_innerが開いたり閉じたりする。
        $(this).next('.accordion_inner').slideToggle();
        $(this).toggleClass("open");
    });
});
```

##小ネタ・その他

###widthの幅を正確に合わせてもうまく収まらない現象の解決方法

例えば、width100%になるように、4つの要素に25%としてもはみ出てしまう現象がある。

これはHTMLの仕様でちょっと差が出たりするかららしい。

そのため、下記のようにすればうまくいく。

```
//親要素
letter-spacing: -1em; // 文字間を詰める

//子要素
letter-spacing: normal; //文字間を元に戻す 
```

###Choromeのオートコンプリート（入力履歴が表示されるやつ）を止める。or背景色を帰る。

`autocomplete属性`をオフにする。

またはCSSで背景色を変更したりもできる。

```html
<form autocomplete="off">
</form>
```

```
input:-webkit-autofill {
    -webkit-box-shadow: 0 0 0px 1000px [背景にしたい色] inset;
}
```

参考：[【Chrome】オートコンプリートのとき、背景を黄色にしない](https://qiita.com/ituki_b/items/094ca16e4ca3f17d8982)

###星の評価機能を追加する

星をクリックしたら評価を送信する機能を実装したいことが多くのwebアプリではあるはず。

そこで今回はいい感じのを実装してみました。

とりあえず、下記をまるっとコピペすればいい感じの星評価が実装できます。

```php:test.blade.php
    <p class="col-10 mx-auto hochi_title">スターで評価</p>
    {{ Form::open(['route'=>['comment.store','episode'=>$episode->id],'id'=>'star_rating_form_js']) }}
    <div class="rating">
        @for ($i = 5; $i >= 1 ; $i--)
        <input type="radio" name="rating" value="{{$i}}" id="r{{$i}}" class="rating_star_js" data-postid={{$episode->id}}
            {{$existRating && $existRating->rating == $i ? 'checked' : ''}}>
        <label for="r{{$i}}"></label>
        @endfor
    </div>
    {{ Form::close() }}

<div class="col-md-8 mx-auto alert alert-warning display_none_js">
        <p id="rating_announce_js" class="rating_p">評価を送信しました。</p>
</div>
```

```php:StarController.php
public function ajasPostStarRating(Request $request)
    {
        $request->validate([
            'rating'=>'required|integer'
        ]);

        $auth = Auth::user();

        $novel_id = Episode::find($request->episode_id)->novel->id;

        $existRating = Rating::where('user_id', $auth->id)->where('novel_id', $novel_id)->first();

        if ($existRating) {
            $existRating->update([
                'rating'=>$request->rating,
            ]);
        } else {
            $existRating = Rating::create([
            'rating'=>$request->rating,
            'user_id'=>$auth->id,
            'novel_id'=>$novel_id
            ]);
        }
        $json = [
            'existRating' => $existRating
        ];

        return response()->json($json);
    }
```

```scss:app.scss
@mixin star-rating($filled-color: #F9BF3B, $empty-color: #444, $size: 40px, $width: 300px) {
    $star--filled: ★;
    $star--empty: ☆;
    width: $width;

    >* {
        float: right;
    }

    // optional initial pulse of stars
    @at-root {
        @keyframes pulse {
            50% {
                color: lighten($empty-color, 10%);
                text-shadow: 0 0 15px lighten($empty-color, 20%);
            }
        }
    }

    label {
        height: 40px;
        width: 20%;
        display: block;
        position: relative;
        cursor: pointer;

        @for $i from 5 through 1 {
            &:nth-of-type(#{$i}):after {
                $animation-delay: $i * .05s;
                animation-delay: $animation-delay;
            }
        }

        &:after {
            transition: all 0.4s ease-out;
            -webkit-font-smoothing: antialiased;
            position: absolute;
            content: '#{$star--empty}';
            color: $empty-color;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            text-align: center;
            font-size: $size;
            animation: 1s pulse ease;
        }

        &:hover:after {
            color: lighten($empty-color, 10%);
            text-shadow: 0 0 15px lighten($empty-color, 10%);
        }
    }

    input {
        display: none;

        &:checked {

            +label:after,
            ~label:after {
                content: '#{$star--filled}';
                color: $filled-color;
                text-shadow: 0 0 20px $filled-color;
            }
        }
    }
}

.rating {
    width: 400px;
    height: 70px;
    margin: 0 auto;
    @include star-rating();
}

.rating_p {
    margin-bottom: 0;
    padding: 0;
    text-align: center;
}


```


```JavaScript:star.js
$(function () {
    $('.rating_star_js').on('change', function (e) {
        episodeId = $(this).data('postid');
        rating = $(this).attr("value");
        $.ajax({
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                },
                url: '/ajasPostStarRating', //routeの記述
                type: 'POST', //受け取り方法の記述（GETもある）
                data: {
                    //コントローラーに渡すパラメーター
                    'episode_id': episodeId,
                    'rating':rating,
                },
            })

            // Ajaxリクエストが成功した場合
            .done(function (data) {
                $(this).prop('checked', true);
                $('.display_none_js').fadeIn();
                $('#rating_announce_js').text(data.existRating.rating + 'つ星の評価を送信しました。');
            })
            // Ajaxリクエストが失敗した場合
            .fail(function (data, xhr, err) {
                //ここの処理はエラーが出た時にエラー内容をわかるようにしておく。
                //とりあえず下記のように記述しておけばエラー内容が詳しくわかります。笑
                console.log('エラー');
                console.log(err);
                console.log(xhr);
            });

        return false;
    });
});

```


###pタグやaタグで文字の高さがおかしい

`line-height`で調整すればOK。

###Laravelがなんか重たいなぁと思った時に確認すること

**・bladeの継承を二重にしていないか。
・N+1問題が発生していないか。**

たぶん、初心者の場合はこの二つが原因の可能性大。

###ベンダーフレフィツクスについて【自動的に付ける方法も】

主なベンダープレフィックスは下記の通り。

```
-moz-　　…… Firefox
-webkit-　…… Google Chrome、Safari
-o-　　　　…… Opera
-ms-　　　…… Internet Explorer
```

自動的にベンダープレフィックスを付けてくれるサービス。
[Autoprefixer CSS online](https://autoprefixer.github.io/)
こちらを利用すれば、いい感じにしてくれる。便利。

各ブラウザの対応状況を調べたい場合は「[Can I use](https://caniuse.com/)」というサービスを利用すれば調べられる。

###aタグで遷移させない方法

下記でOK。すごく簡単。

```html

<a href="javascript:void(0)">遷移しないよ</a>
```

でもこの方法はあまりよろしく無いらしい。

なので、下記のようbuttonタグで書き換えるのがオススメ。

```html

<button type="button">遷移しないよ</button>
```