CSS & JS Effect – Dialog Modal

效果

 

参考:

Youtube – Create a Simple Popup Modal

Youtube – Create a Modal (Popup) with HTML/CSS and JavaScript

HTML – Native Dialog Modal

Boostrap Modal

Angular Material Dialog

 

重点

1. modal 就是一个 position: fixed 的大 overlay 黑影, 同时里面有居中的 content box.

2. modal 原本是 hide 起来的, 点击后 show 就可以了.

 

HTML

<body>
    <button id="js-open-modal-btn" class="open-modal-btn">Open Modal</button>
    <div id="js-modal" class="modal">
      <div class="content"> 
        <div class="header">
        <h1>You are The Winner!</h1>
          <button id="js-close-modal-btn" class="close-modal-btn"><i class="fa-solid fa-xmark"></i></button>
        </div>
      <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellat id, rem veniam ratione modi laboriosam consectetur vitae illum perspiciatis recusandae!</p>
      </div>
    </div>
</body>

modal 负责 backdrop 黑影

content 则是中间白色的内容区域

 

CSS Style

.open-modal-btn{
  margin-top: 17rem;
  margin-inline: auto;
  display: block;
}

.modal {
  visibility: hidden; // show / hide control

  position: fixed; // 定位
  inset: 0; // full viewport

  background-color: rgb(0 0 0 / 0.2); // backdrop color

  // 居中 content
  display: flex;
  justify-content: center;
  align-items: center;
 
  .content {
    padding: 2rem 3rem;
    max-width: 1024px;
    background-color: white; // 因为 modal 是透明黑, 所以这里要 set 回白色
    box-shadow: 0 2px 4px rgb(0 0 0 / 0.2); // 影子

    .header{
      display: flex;
      justify-content: space-between;
      align-items: center;
      
      font-size: 3rem;

      .close-modal-btn {
        background-color: white;
        font-size: inherit;
        color: black;
        border-width: 0;
        cursor: pointer;
      }
    }

    p{
      margin-top: 2rem;
      font-size: 2rem;
    }
  }

  &.show {
    visibility: initial; // clear visibility to show
  }
}
View Code

看注释的地方解释就可以了, 其它是点缀而已

 

JavaScript

const modal = document.querySelector<HTMLElement>('#js-modal')!;
const openModalBtn = document.querySelector<HTMLButtonElement>('#js-open-modal-btn')!;
const closeModalBtn = document.querySelector<HTMLButtonElement>('#js-close-modal-btn')!;
openModalBtn.addEventListener('click', () => {
  modal.classList.add('show');
});

closeModalBtn.addEventListener('click', () => {
  modal.classList.remove('show');
});

只是简单的点击 add class 而已

 

Body Scroll IOS Safari Problem

modal 开启后, 通常体验不允许 body scroll. 一般的做法是给 body overflow: hidden

但是这个在 Safari 有 bug. 目前 status 是说已经 fixed 了, 但是我没用最新的 safari 测试, 所以不确定.

解决方法是让 body position fixed, 然后修改它的 top 到当前的 scroll top.

下面是我封装的方法

// note: 来龙去脉
// safari 没有办法通过 overflow hide 去阻止 body scroll
// 所以只能把 body 定位变成没有 scroll
// 这个 safari 问题已经很多年的了
// tesla, angular, stackoverflow 也是用这个方案去破
// bootstrap 倒没有处理这个, 比较 noob
// refer:
// https://bugs.webkit.org/show_bug.cgi?id=153852
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block

const showedModals: string[] = [];

export function toggleModal(modalName: string): void {
  showedModals.includes(modalName) ? closeModal(modalName) : openModal(modalName);
}

export function openModal(modalName: string): void {
  if (showedModals.length === 0) {
    const currentScrollTop = (document.scrollingElement ?? document.documentElement).scrollTop;
    document.body.style.position = 'fixed';
    document.body.style.width = '100%';
    document.body.style.top = `-${currentScrollTop}px`;
  }
  showedModals.push(modalName);
}

export function closeModal(modalName: string): void {
  if (!showedModals.includes(modalName)) {
    throw new Error(`Showed modals doesn't contains modal name: ${modalName}`);
  }
  if (showedModals.length === 1) {
    const recoveryScrollTop = Math.abs(parseFloat(document.body.style.top));
    document.body.style.removeProperty('position');
    document.body.style.removeProperty('width');
    document.body.style.removeProperty('top');
    (document.scrollingElement ?? document.documentElement).scrollTop = recoveryScrollTop;
  }
  const indexOf = showedModals.indexOf(modalName);
  showedModals.splice(indexOf, 1);
}

 

Mobile Back Button Close Dialog

大家习惯按手机的 back button 来试图关闭 Modal,

其实这是一个不正确的操作. 因为 back 会直接跳去上一页而不是关闭 Modal.

为了防止这样的错误体验. 我们可以做一点手脚.

step 1: 在打开 Modal的同时 history.push 放入 query param ?modal="show" (这样之后就可以 back 了)

step 2: 把关闭的操作 改成 history.back (统一关闭的方式)

step 3: 监听 onpopstate, 一旦发生就关闭 Modal.

step 4: 如果用户 refresh 那我们需要在 page load 的时候把 ?modal=show 用 history.replace 移除掉

以上的方法思路只是 for 简单场景的. 如果有多层 modal 或者更复杂的情况就 cover 不了了.

JS 代码:

const modal = document.querySelector<HTMLElement>('#js-modal')!;
const openModalBtn = document.querySelector<HTMLButtonElement>('#js-open-modal-btn')!;
const closeModalBtn = document.querySelector<HTMLButtonElement>('#js-close-modal-btn')!;

// 处理 page load
const { pathname, search, hash } = location;
const queryParams = new URLSearchParams(search);
if (queryParams.has('whatsAppDialog')) {
  queryParams.delete('whatsAppDialog');
  const newSearch = queryParams.toString() !== '' ? `?${queryParams.toString()}` : '';
  history.replaceState(null, '', `${pathname}${newSearch}${hash}`);
}

openModalBtn.addEventListener('click', () => {
  modal.classList.add('show');
  // pushState
  const { pathname, search, hash } = location;
  const params = new URLSearchParams(search);
  params.set('whatsAppDialog', 'open');
  history.pushState(null, '', `${pathname}?${params.toString()}${hash}`);

  // listen to 'back button'
  window.addEventListener(
    'popstate',
    () => {
      modal.classList.remove('show');
    },
    { once: true }
  );
});

closeModalBtn.addEventListener('click', () => {
  history.back(); // back
});
View Code

另外, 未来或许可以用 Navigation API 实现, 可能会更方便. 以后才研究. 毕竟现在许多 browser 还不支持.

 

其它没有提到的

1. backdrop click, Keyboard Esc 关闭.

2. show 的 animation, backdrop fadein, content scale in

3. z-index 问题. 要确保 z-index 够高最好是把 modal 放到 body 最下方 (Angular Material 就这么做的)

但放出去后要注意 CSS Style 哦. element 结构在外面了就不可以 depend on ancestor 了.

 

posted @ 2022-04-17 19:33  兴杰  阅读(56)  评论(0编辑  收藏  举报