diff --git a/package.json b/package.json
index 5f35c5e013..aa2ce56341 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
 	"name": "misskey",
 	"author": "syuilo <i@syuilo.com>",
-	"version": "0.0.3878",
+	"version": "0.0.3880",
 	"license": "MIT",
 	"description": "A miniblog-based SNS",
 	"bugs": "https://github.com/syuilo/misskey/issues",
diff --git a/src/web/app/common/views/components/autocomplete.vue b/src/web/app/common/views/components/autocomplete.vue
index f31a624746..401eb043d0 100644
--- a/src/web/app/common/views/components/autocomplete.vue
+++ b/src/web/app/common/views/components/autocomplete.vue
@@ -87,22 +87,9 @@ export default Vue.extend({
 			el.addEventListener('mousedown', this.onMousedown);
 		});
 
-		if (this.type == 'user') {
-			(this as any).api('users/search_by_username', {
-				query: this.q,
-				limit: 30
-			}).then(users => {
-				this.users = users;
-				this.fetching = false;
-			});
-		} else if (this.type == 'emoji') {
-			const matched = [];
-			emjdb.some(x => {
-				if (x.name.indexOf(this.q) > -1 && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
-				return matched.length == 30;
-			});
-			this.emojis = matched;
-		}
+		this.exec();
+
+		this.$watch('q', this.exec);
 	},
 	beforeDestroy() {
 		this.textarea.removeEventListener('keydown', this.onKeydown);
@@ -112,6 +99,35 @@ export default Vue.extend({
 		});
 	},
 	methods: {
+		exec() {
+			if (this.type == 'user') {
+				const cache = sessionStorage.getItem(this.q);
+				if (cache) {
+					const users = JSON.parse(cache);
+					this.users = users;
+					this.fetching = false;
+				} else {
+					(this as any).api('users/search_by_username', {
+						query: this.q,
+						limit: 30
+					}).then(users => {
+						this.users = users;
+						this.fetching = false;
+
+						// キャッシュ
+						sessionStorage.setItem(this.q, JSON.stringify(users));
+					});
+				}
+			} else if (this.type == 'emoji') {
+				const matched = [];
+				emjdb.some(x => {
+					if (x.name.indexOf(this.q) > -1 && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
+					return matched.length == 30;
+				});
+				this.emojis = matched;
+			}
+		},
+
 		onMousedown(e) {
 			if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close();
 		},
@@ -152,9 +168,6 @@ export default Vue.extend({
 					cancel();
 					this.selectNext();
 					break;
-
-				default:
-					this.close();
 			}
 		},
 
@@ -189,6 +202,7 @@ export default Vue.extend({
 	background #fff
 	border solid 1px rgba(0, 0, 0, 0.1)
 	border-radius 4px
+	transition top 0.1s ease, left 0.1s ease
 
 	> ol
 		display block
diff --git a/src/web/app/common/views/directives/autocomplete.ts b/src/web/app/common/views/directives/autocomplete.ts
index e221cce71e..3440c4212a 100644
--- a/src/web/app/common/views/directives/autocomplete.ts
+++ b/src/web/app/common/views/directives/autocomplete.ts
@@ -6,7 +6,6 @@ export default {
 		const self = el._autoCompleteDirective_ = {} as any;
 		self.x = new Autocomplete(el, vn.context, binding.value);
 		self.x.attach();
-		console.log(vn.context);
 	},
 
 	unbind(el, binding, vn) {
@@ -23,6 +22,7 @@ class Autocomplete {
 	private textarea: any;
 	private vm: any;
 	private model: any;
+	private currentType: string;
 
 	private get text(): string {
 		return this.vm[this.model];
@@ -67,24 +67,32 @@ class Autocomplete {
 	 * テキスト入力時
 	 */
 	private onInput() {
-		this.close();
-
 		const caret = this.textarea.selectionStart;
 		const text = this.text.substr(0, caret);
 
 		const mentionIndex = text.lastIndexOf('@');
 		const emojiIndex = text.lastIndexOf(':');
 
+		let opened = false;
+
 		if (mentionIndex != -1 && mentionIndex > emojiIndex) {
 			const username = text.substr(mentionIndex + 1);
-			if (!username.match(/^[a-zA-Z0-9-]+$/)) return;
-			this.open('user', username);
+			if (username != '' && username.match(/^[a-zA-Z0-9-]+$/)) {
+				this.open('user', username);
+				opened = true;
+			}
 		}
 
 		if (emojiIndex != -1 && emojiIndex > mentionIndex) {
 			const emoji = text.substr(emojiIndex + 1);
-			if (!emoji.match(/^[\+\-a-z0-9_]+$/)) return;
-			this.open('emoji', emoji);
+			if (emoji != '' && emoji.match(/^[\+\-a-z0-9_]+$/)) {
+				this.open('emoji', emoji);
+				opened = true;
+			}
+		}
+
+		if (!opened) {
+			this.close();
 		}
 	}
 
@@ -92,8 +100,10 @@ class Autocomplete {
 	 * サジェストを提示します。
 	 */
 	private open(type, q) {
-		// 既に開いているサジェストは閉じる
-		this.close();
+		if (type != this.currentType) {
+			this.close();
+		}
+		this.currentType = type;
 
 		//#region サジェストを表示すべき位置を計算
 		const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart);
@@ -104,21 +114,27 @@ class Autocomplete {
 		const y = rect.top + caretPosition.top - this.textarea.scrollTop;
 		//#endregion
 
-		// サジェスト要素作成
-		this.suggestion = new MkAutocomplete({
-			propsData: {
-				textarea: this.textarea,
-				complete: this.complete,
-				close: this.close,
-				type: type,
-				q: q,
-				x,
-				y
-			}
-		}).$mount();
+		if (this.suggestion) {
+			this.suggestion.x = x;
+			this.suggestion.y = y;
+			this.suggestion.q = q;
+		} else {
+			// サジェスト要素作成
+			this.suggestion = new MkAutocomplete({
+				propsData: {
+					textarea: this.textarea,
+					complete: this.complete,
+					close: this.close,
+					type: type,
+					q: q,
+					x,
+					y
+				}
+			}).$mount();
 
-		// 要素追加
-		document.body.appendChild(this.suggestion.$el);
+			// 要素追加
+			document.body.appendChild(this.suggestion.$el);
+		}
 	}
 
 	/**