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 => {