From fda4003ba4944f2e2d3bdd8a42f2ddc510aefa5c Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Sun, 19 Nov 2017 00:04:40 +0900 Subject: [PATCH] =?UTF-8?q?=E3=81=84=E3=81=84=E6=84=9F=E3=81=98=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/desktop/scripts/scroll-follower.ts | 57 +++++++++++++++++++ src/web/app/desktop/tags/home.tag | 12 ++-- src/web/app/desktop/tags/user.tag | 37 ++++++++---- 3 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 src/web/app/desktop/scripts/scroll-follower.ts diff --git a/src/web/app/desktop/scripts/scroll-follower.ts b/src/web/app/desktop/scripts/scroll-follower.ts new file mode 100644 index 000000000..e99f125cc --- /dev/null +++ b/src/web/app/desktop/scripts/scroll-follower.ts @@ -0,0 +1,57 @@ +/** + * 要素をスクロールに追従させる + */ +export default class ScrollFollower { + private follower: Element; + private containerTop: number; + private topPadding: number; + + constructor(follower: Element, topPadding: number) { + //#region + this.follow = this.follow.bind(this); + //#endregion + + this.follower = follower; + this.containerTop = follower.getBoundingClientRect().top; + this.topPadding = topPadding; + + window.addEventListener('scroll', this.follow); + window.addEventListener('resize', this.follow); + } + + /** + * 追従解除 + */ + public dispose() { + window.removeEventListener('scroll', this.follow); + window.removeEventListener('resize', this.follow); + } + + private follow() { + const windowBottom = window.scrollY + window.innerHeight; + const windowTop = window.scrollY + this.topPadding; + + const rect = this.follower.getBoundingClientRect(); + //const followerHeight = rect.height + this.containerTop; + const followerBottom = (rect.top + window.scrollY) + rect.height; + + // スクロールの上部(+余白)がフォロワーコンテナの上部よりも上方にある + if (window.scrollY + this.topPadding < this.containerTop) { + // フォロワーをコンテナの最上部に合わせる + (this.follower.parentNode as any).style.marginTop = '0px'; + return; + } + + // スクロールの下部がフォロワーの下部よりも下方にある かつ 表示領域の縦幅がフォロワーの縦幅よりも狭い + if (windowBottom > followerBottom && rect.height > (window.innerHeight - this.topPadding)) { + // フォロワーの下部をスクロール下部に合わせる + const top = (windowBottom - rect.height) - this.containerTop; + (this.follower.parentNode as any).style.marginTop = `${top}px`; + // スクロールの上部(+余白)がフォロワーの上部よりも上方にある または 表示領域の縦幅がフォロワーの縦幅よりも広い + } else if ((windowTop < rect.top + window.scrollY || rect.height < (window.innerHeight - this.topPadding))) { + // フォロワーの上部をスクロール上部(+余白)に合わせる + const top = windowTop - this.containerTop; + (this.follower.parentNode as any).style.marginTop = `${top}px`; + } + } +} diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag index 92f91c084..a2a372a0e 100644 --- a/src/web/app/desktop/tags/home.tag +++ b/src/web/app/desktop/tags/home.tag @@ -184,6 +184,7 @@ import uuid from 'uuid'; import Sortable from 'sortablejs'; import dialog from '../scripts/dialog'; + import ScrollFollower from '../scripts/scroll-follower'; this.mixin('i'); this.mixin('api'); @@ -242,11 +243,8 @@ } if (!this.opts.customize) { - this.containerTop = this.refs.main.getBoundingClientRect().top; - this.headerHight = this.root.getBoundingClientRect().top; - - window.addEventListener('scroll', this.followWidgets); - window.addEventListener('resize', this.followWidgets); + this.scrollFollowerLeft = new ScrollFollower(this.refs.left, this.root.getBoundingClientRect().top); + this.scrollFollowerRight = new ScrollFollower(this.refs.right, this.root.getBoundingClientRect().top); } }); @@ -256,8 +254,8 @@ }); if (!this.opts.customize) { - window.removeEventListener('scroll', this.followWidgets); - window.removeEventListener('resize', this.followWidgets); + this.scrollFollowerLeft.dispose(); + this.scrollFollowerRight.dispose(); } }); diff --git a/src/web/app/desktop/tags/user.tag b/src/web/app/desktop/tags/user.tag index d944bb33a..333dd11a0 100644 --- a/src/web/app/desktop/tags/user.tag +++ b/src/web/app/desktop/tags/user.tag @@ -607,20 +607,24 @@ <mk-user-home> <div> - <mk-user-profile user={ user }/> - <mk-user-photos user={ user }/> - <mk-user-followers-you-know if={ SIGNIN && I.id !== user.id } user={ user }/> - <p>%i18n:desktop.tags.mk-user.last-used-at%: <b><mk-time time={ user.last_used_at }/></b></p> + <div ref="left"> + <mk-user-profile user={ user }/> + <mk-user-photos user={ user }/> + <mk-user-followers-you-know if={ SIGNIN && I.id !== user.id } user={ user }/> + <p>%i18n:desktop.tags.mk-user.last-used-at%: <b><mk-time time={ user.last_used_at }/></b></p> + </div> </div> <main> <mk-post-detail if={ user.pinned_post } post={ user.pinned_post } compact={ true }/> <mk-user-timeline ref="tl" user={ user }/> </main> <div> - <mk-calendar-widget warp={ warp } start={ new Date(user.created_at) }/> - <mk-activity-widget user={ user }/> - <mk-user-frequently-replied-users user={ user }/> - <div class="nav"><mk-nav-links/></div> + <div ref="right"> + <mk-calendar-widget warp={ warp } start={ new Date(user.created_at) }/> + <mk-activity-widget user={ user }/> + <mk-user-frequently-replied-users user={ user }/> + <div class="nav"><mk-nav-links/></div> + </div> </div> <style> :scope @@ -629,7 +633,8 @@ margin 0 auto max-width 1200px - > * + > main + > div > div > *:not(:last-child) margin-bottom 16px @@ -645,7 +650,7 @@ width 275px margin 0 - &:first-child + &:first-child > div padding 16px 0 16px 16px > p @@ -656,7 +661,7 @@ font-size 0.8em color #aaa - &:last-child + &:last-child > div padding 16px 16px 16px 0 > .nav @@ -675,6 +680,8 @@ </style> <script> + import ScrollFollower from '../scripts/scroll-follower'; + this.mixin('i'); this.user = this.opts.user; @@ -683,6 +690,14 @@ this.refs.tl.on('loaded', () => { this.trigger('loaded'); }); + + this.scrollFollowerLeft = new ScrollFollower(this.refs.left, this.parent.root.getBoundingClientRect().top); + this.scrollFollowerRight = new ScrollFollower(this.refs.right, this.parent.root.getBoundingClientRect().top); + }); + + this.on('unmount', () => { + this.scrollFollowerLeft.dispose(); + this.scrollFollowerRight.dispose(); }); this.warp = date => {