diff --git a/src/directives/body_scroll_lock.js b/src/directives/body_scroll_lock.js
new file mode 100644
index 0000000000000000000000000000000000000000..6ab20c3f18cfe2e0403cd4cda5cb0fbca3b1eba6
--- /dev/null
+++ b/src/directives/body_scroll_lock.js
@@ -0,0 +1,69 @@
+import * as bodyScrollLock from 'body-scroll-lock'
+
+let previousNavPaddingRight
+let previousAppBgWrapperRight
+
+const disableBodyScroll = (el) => {
+  const scrollBarGap = window.innerWidth - document.documentElement.clientWidth
+  bodyScrollLock.disableBodyScroll(el, {
+    reserveScrollBarGap: true
+  })
+  setTimeout(() => {
+    // If previousNavPaddingRight is already set, don't set it again.
+    if (previousNavPaddingRight === undefined) {
+      const navEl = document.getElementById('nav')
+      previousNavPaddingRight = window.getComputedStyle(navEl).getPropertyValue('padding-right')
+      navEl.style.paddingRight = previousNavPaddingRight ? `calc(${previousNavPaddingRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
+    }
+    // If previousAppBgWrapeprRight is already set, don't set it again.
+    if (previousAppBgWrapperRight === undefined) {
+      const appBgWrapperEl = document.getElementById('app_bg_wrapper')
+      previousAppBgWrapperRight = window.getComputedStyle(appBgWrapperEl).getPropertyValue('right')
+      appBgWrapperEl.style.right = previousAppBgWrapperRight ? `calc(${previousAppBgWrapperRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
+    }
+    document.body.classList.add('scroll-locked')
+  })
+}
+
+const enableBodyScroll = (el) => {
+  setTimeout(() => {
+    if (previousNavPaddingRight !== undefined) {
+      document.getElementById('nav').style.paddingRight = previousNavPaddingRight
+      // Restore previousNavPaddingRight to undefined so disableBodyScroll knows it can be set again.
+      previousNavPaddingRight = undefined
+    }
+    if (previousAppBgWrapperRight !== undefined) {
+      document.getElementById('app_bg_wrapper').style.right = previousAppBgWrapperRight
+      // Restore previousAppBgWrapperRight to undefined so disableBodyScroll knows it can be set again.
+      previousAppBgWrapperRight = undefined
+    }
+    document.body.classList.remove('scroll-locked')
+  })
+  bodyScrollLock.enableBodyScroll(el)
+}
+
+const directive = {
+  inserted: (el, binding) => {
+    if (binding.value) {
+      disableBodyScroll(el)
+    }
+  },
+  componentUpdated: (el, binding) => {
+    if (binding.oldValue === binding.value) {
+      return
+    }
+
+    if (binding.value) {
+      disableBodyScroll(el)
+    } else {
+      enableBodyScroll(el)
+    }
+  },
+  unbind: (el) => {
+    enableBodyScroll(el)
+  }
+}
+
+export default (Vue) => {
+  Vue.directive('body-scroll-lock', directive)
+}
diff --git a/src/main.js b/src/main.js
index b3256e8ed7b6e583eb6ec574955451246e1991c5..ffaf0ad5b5467e14b495b29c855aeb2bb7f58ff3 100644
--- a/src/main.js
+++ b/src/main.js
@@ -26,6 +26,7 @@ import messages from './i18n/messages.js'
 import VueChatScroll from 'vue-chat-scroll'
 import VueClickOutside from 'v-click-outside'
 import PortalVue from 'portal-vue'
+import VBodyScrollLock from './directives/body_scroll_lock'
 import VTooltip from 'v-tooltip'
 
 import afterStoreSetup from './boot/after_store.js'
@@ -38,6 +39,7 @@ Vue.use(VueI18n)
 Vue.use(VueChatScroll)
 Vue.use(VueClickOutside)
 Vue.use(PortalVue)
+Vue.use(VBodyScrollLock)
 Vue.use(VTooltip)
 
 const i18n = new VueI18n({