• 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 了.

  • 相关阅读:
    Stock Transfer I
    Negative Stock in "Stock in Transit"
    ZFINDEXITS
    Plan Driven Procurement III: Classic Scenario in SRM 5.0
    C++builder 创建进程之CreateProcess函数
    常用DOS命令
    【转】程序员的几个级别
    几本书
    OOP SOLID原则
    SSRS 使用总结
  • 原文地址:https://www.cnblogs.com/keatkeat/p/16156959.html
Copyright © 2020-2023  润新知