Stimulus + TypeScript + Slim で「もっと見る」「閉じる」を作る
要件
・viewはslim ・可変なliが渡ってくるsidemenuでもっとみる/とじるをぬるっと動かす
とりあえずviewはこんな感じ
- unique = SecureRandom.hex(8) .show-more[data-controller="show-more"] input.show-more__check[type="checkbox" id="show-more__check-#{unique}"] .show-more__content[data-target="show-more.content"] .show-more__conetnt-wrapper = yield label.show-more__label[for="show-more__check-#{unique}" data-target="show-more.label" data-action="click->show-more#toggle"]
複数箇所で使われることを想定して、特定のcheckboxに対するlabelであることを担保するためにidをuniqueにしました
すべてを囲う
show-more
に対してdata-controller="show-more"
をつけることで、stimulus のshow_more_controller.ts
にアクセスできます。もちろん名前はなんでもOK。labelの「もっとみる」「とじる」をcssで変更させたかったので、inputでopen/closeをもつようにしました。それとあわせてjs側でもそれを担保するために、controller内で
state
的な値を持たせています。これを使う側で中身を可変にできるために、yieldを挟んでいます。一度限りしか使わないパーツならyieldは不要で、その部分にlist的なものを入れてあげればOKです。
続いてStimulus側
import { Controller } from 'stimulus' export default class ShowMoreController extends Controller { static targets = ['content', 'label'] contentTarget!: HTMLElement private isShow = false private static readonly DEFAULT_CONTENT_HEIGHT: number = 220 toggle(): void { if (this.isShow) { this.updateShowStatus(false) this.contentTarget.style.height = `${ShowMoreController.DEFAULT_CONTENT_HEIGHT}px` } else { this.updateShowStatus(true) this.contentTarget.style.height = `${this.contentHeight}px` } } private updateShowStatus(flag: boolean): void { this.isShow = flag } private get contentHeight(): number { if (!this.contentTarget || !this.contentTarget.firstElementChild) { return ShowMoreController.DEFAULT_CONTENT_HEIGHT } return this.contentTarget.firstElementChild.clientHeight } }
一度labelをクリックしたら、
toggle()
が呼ばれます。基本的にもっとみるは最初閉じられているので、デフォルトでfalseが設定されているisShow
をみて、開くのか閉じるのかを決定させます。ここが大事!今回は可変な要素に対してtransition(アニメーション) をつけたかったのですが、height: autoに対して基本的にtransitionをつけることができませんでした。なので、jsで要素のheightを変更させるようにした感じです。もちろんここをjqueryで書いてもいいのですが、jqueryはちょっと...という場合にこの方法を採りました。
定数として定義している
DEFAULT_CONTENT_HEIGHT
は最初にちょっと出す部分のheightである220を設定していますが、そこは要件によりよしなに変更していただければ。
最後にscss
.show-more { margin-bottom: $space-XXL; .show-more__content { position: relative; overflow: hidden; height: 220px; transition: all 400ms ease; } .show-more__content::before { display: block; position: absolute; bottom: 0; left: 0; width: 100%; content: ""; height: 50px; background: $co-white; opacity: .6; } .show-more__label { margin: 0 auto; position: absolute; left: 50%; width: 100%; height: 44px; line-height: 44px; bottom: -20px; transform: translateX(-50%); background-color: $co-light-gray; border-radius: 0 0 $round $round; color: $co-dark-gray; font-weight: bold; cursor: pointer; text-align: center; } .show-more__label::before { content: '続きを読む'; margin-right: $space-S; } // FIXME font awesome的なのを入れて自作を回避したい .show-more__label::after { font-size: 4px; content: '▼'; } .show-more__check { display: none; } .show-more__check:checked ~ .show-more__label { bottom: 0px; } .show-more__check:checked ~ .show-more__label::before { content: '閉じる'; margin-right: $space-S; } .show-more__check:checked ~ .show-more__label::after { font-size: 4px; content: '▲'; } .show-more__check:checked ~ .show-more__content::before { display: none; } }
- contentで文字を定義していますが、ここはjsやslim側で定義してあげてもよかったかなと。I18n使ってたりすると辛いです。
参考
Stimulusの詳しい使い方はまずはリファレンス: stimulusjs.org
カンタンに文法をみるならこの記事がよかった: qiita.com