山田 典明 | @noliaki
昭和57年9月28日生 AB型 | 37歳
ActionScript3生まれ jQuery育ち
$なヤツはだいたい友達
$なヤツとだいたい同じ
アコーディオンというような呼ばれ方をするUI
モーダル
まとめ
$('.hoge').slideUp()
// or
$('.hoge').slideDown()
最近、NuxtでWebサイト作ってる
データは外部で管理してて、データ取得後、DOM構築までしなきゃならないので、Vueを使ってる
たくさんあるので、Vueを使ってる
<li class="posts-item" :class="{ '-active': isShownBody }">
<button
type="button"
class="posts-item-btn"
@click.prevent="isShownBody = !isShownBody"
>{{ post.title }}</button>
<transition
name="post-body"
@enter="enter"
@afterEnter="afterTransition"
@leave="leave"
@afterLeave="afterTransition"
>
<div v-show="isShownBody">
<div class="posts-item-body">{{ post.body }}</div>
</div>
</transition>
</li>
Vue.component('template-posts-item', {
// ~~~~~~
methods: {
enter(el: HTMLElement): void {
// 中身がはみ出して表示されてないようにする
el.style.overflow = 'hidden'
// 最初の高さは0
el.style.height = '0px'
// 最終的な高さを代入
el.style.height = `${el.scrollHeight}px`
},
leave(el: HTMLElement): void {
// 中身がはみ出して表示されてないようにする
el.style.overflow = 'hidden'
// 最初の高さはコンテンツの高さ分
el.style.height = `${el.scrollHeight}px`
// 最終的に高さの0を代入
el.style.height = '0px'
},
afterTransition(el: HTMLElement): void {
el.style.overflow = ''
el.style.height = ''
}
}
})
.post-body-enter-active,
.post-body-leave-active {
-webkit-transition: height 400ms ease;
transition: height 400ms ease;
}
CSSはこんな感じ
Vue.component('template-posts-item', {
// ~~~~~~
methods: {
enter(el: HTMLElement): void {
// 中身がはみ出して表示されてないようにする
el.style.overflow = 'hidden'
// 最初の高さは0
el.style.height = '0px'
// 最終的な高さを代入
el.style.height = `${el.scrollHeight}px`
},
leave(el: HTMLElement): void {
// 中身がはみ出して表示されてないようにする
el.style.overflow = 'hidden'
// 最初の高さはコンテンツの高さ分
el.style.height = `${el.scrollHeight}px`
// 最終的に高さの0を代入
el.style.height = '0px'
},
afterTransition(el: HTMLElement): void {
el.style.overflow = ''
el.style.height = ''
}
}
})
強制レイアウト
強制レイアウト
Vue.component('template-posts-item', {
// ~~~~~~
methods: {
enter(el: HTMLElement): void {
// 中身がはみ出して表示されてないようにする
el.style.overflow = 'hidden'
// 最初の高さは0
el.style.height = '0px'
// 最終的な高さを代入
el.style.height = `${el.scrollHeight}px`
},
leave(el: HTMLElement): void {
// 中身がはみ出して表示されてないようにする
el.style.overflow = 'hidden'
// 最初の高さはコンテンツの高さ分
el.style.height = `${el.scrollHeight}px`
const scrollHeight: number = el.scrollHeight
// 最終的に高さの0を代入
el.style.height = `${scrollHeight - scrollHeight}px`
},
afterTransition(el: HTMLElement): void {
el.style.overflow = ''
el.style.height = ''
}
}
})
ちょっと変更
requestAnimationFrameの中のrequestAnimationFrameは
次のフレームで実行されるんだよ🤗
function nextFrame(cb: () => void): void {
window.requestAnimationFrame(() =>
window.requestAnimationFrame(cb)
)
}
Vue.component('template-posts-item', {
// ~~~~~~
methods: {
enter(el: HTMLElement): void {
el.style.overflow = 'hidden'
const height: number = el.scrollHeight
el.style.height = '0'
nextFrame((): void => {
el.style.height = `${height}px`
})
},
leave(el: HTMLElement): void {
el.style.overflow = 'hidden'
el.style.height = `${el.scrollHeight}px`
nextFrame((): void => {
el.style.height = '0px'
})
},
afterTransition(el: HTMLElement): void {
el.style.overflow = ''
el.style.height = ''
}
}
})
ブラウザの表示領域の上下左右中央に配置したい
モーダルが表示されてるときはページ自体のスクロールはしないようにしたい
<transition name="modal" @after-leave="afterLeave">
<div class="modal" v-show="isShownModal">
<div class="modal-bg" @click.prevent="hideModal"></div>
<div class="modal-content" v-if="target">
<transition :name="detailTransitionName" mode="out-in">
<div class="detail" :key="target.id">
<div class="detail-thumbnail">
<img :src="target.thumbnailUrl" :alt="target.title">
</div>
<p>{{ target.title }}</p>
</div>
</transition>
<button
type="button"
class="detail-prev"
@click.prevent="onClickPrev"></button>
<button
type="button"
class="detail-next"
@click.prevent="onClickNext"></button>
</div>
</div>
</transition>
.modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
overflow: auto;
padding: 1.5rem;
}
.modal-bg {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.8);
}
.modal-content {
position: relative;
margin: auto;
width: 40%;
}
html.-clipped {
overflow: hidden;
}
const htmlEl: HTMLHtmlElement =
document.querySelector('html')
const clippedClassName: string = '-clipped'
// ~~~~~~~~~~~~~~~~~~
htmlEl.classList.add(clippedClassName)
if (document.documentElement.clientHeight < bodyEl.scrollHeight) {
bodyEl.style.paddingRight = `${scrollBarWidth}px`
}
Bootstrapを参考
ありふれたUIで決まりきった実装をしていた部分を改めて実装したりすると、考える必要のあることや知らなかったことを発見したりできる
ライブラリなどに頼るのも解決策だが、かゆいところに手が届かないことが少なくないので、どのような実装になってるのか調べてみるのでも新しい発見がある