You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移动端键盘开启时关闭Dialog组件无法卸载的技术问题

解决HeadlessUI Dialog移动端键盘开启时关闭后未卸载的问题

问题复现

  • 移动端打开弹窗
  • 聚焦输入框唤起虚拟键盘
  • 保持键盘开启状态,点击弹窗背景关闭弹窗
  • 弹窗完成过渡消失后,组件未卸载,阻挡下层所有元素交互

核心原因

移动端键盘唤起会改变视窗高度,导致HeadlessUI的Transition组件在处理退场动画时,无法正确同步Dialog的open状态与过渡完成后的卸载逻辑;同时输入框未失焦也可能引发DOM状态异常,使得弹窗底层元素仍保留在页面中阻挡交互。

解决方案

1. 强制过渡结束后卸载Dialog

确保Transition组件设置unmount={true},让组件在过渡动画完全结束后自动卸载:

import { Dialog, Transition } from '@headlessui/react';
import { useState, Fragment } from 'react';

function AppModal({ open, setOpen }) {
  return (
    <Transition appear show={open} as={Fragment} unmount={true}>
      <Dialog as="div" className="relative z-10" onClose={setOpen}>
        {/* 背景遮罩过渡 */}
        <Transition.Child
          as={Fragment}
          enter="ease-out duration-300"
          enterFrom="opacity-0"
          enterTo="opacity-100"
          leave="ease-in duration-200"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <div className="fixed inset-0 bg-black/25" />
        </Transition.Child>

        {/* 弹窗内容容器 */}
        <div className="fixed inset-0 overflow-y-auto">
          <div className="flex min-h-full items-center justify-center p-4">
            <Transition.Child
              as={Fragment}
              enter="ease-out duration-300"
              enterFrom="opacity-0 scale-95"
              enterTo="opacity-100 scale-100"
              leave="ease-in duration-200"
              leaveFrom="opacity-100 scale-100"
              leaveTo="opacity-0 scale-95"
            >
              <Dialog.Panel className="w-full max-w-md rounded-2xl bg-white p-6 shadow-xl transition-all">
                <Dialog.Title className="text-lg font-medium text-gray-900">示例弹窗</Dialog.Title>
                <div className="mt-4">
                  <input 
                    type="text" 
                    className="w-full border rounded p-2" 
                    placeholder="输入内容"
                  />
                </div>
                <div className="mt-4">
                  <button
                    className="rounded-md bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200"
                    onClick={() => setOpen(false)}
                  >
                    关闭弹窗
                  </button>
                </div>
              </Dialog.Panel>
            </Transition.Child>
          </div>
        </div>
      </Dialog>
    </Transition>
  );
}

2. 关闭弹窗时强制失焦输入框

在Dialog的关闭回调中,手动让输入框失焦,避免键盘唤起状态下的DOM异常:

import { Dialog, Transition } from '@headlessui/react';
import { useState, Fragment, useRef } from 'react';

function AppModal({ open, setOpen }) {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleClose = () => {
    // 强制输入框失焦
    inputRef.current?.blur();
    setOpen(false);
  };

  return (
    <Transition appear show={open} as={Fragment} unmount={true}>
      <Dialog as="div" className="relative z-10" onClose={handleClose}>
        {/* ... 省略遮罩与容器代码 ... */}
        <Dialog.Panel className="w-full max-w-md rounded-2xl bg-white p-6 shadow-xl transition-all">
          <Dialog.Title className="text-lg font-medium text-gray-900">示例弹窗</Dialog.Title>
          <div className="mt-4">
            <input 
              ref={inputRef}
              type="text" 
              className="w-full border rounded p-2" 
              placeholder="输入内容"
            />
          </div>
          <div className="mt-4">
            <button
              className="rounded-md bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200"
              onClick={handleClose}
            >
              关闭弹窗
            </button>
          </div>
        </Dialog.Panel>
      </Dialog>
    </Transition>
  );
}

3. 监听视窗高度变化同步状态

移动端键盘唤起会改变window.innerHeight,监听该变化,确保弹窗在关闭状态下完全卸载:

import { Dialog, Transition } from '@headlessui/react';
import { useState, Fragment, useEffect } from 'react';

function AppModal({ open, setOpen }) {
  useEffect(() => {
    const handleResize = () => {
      if (!open) {
        // 手动清理残留的弹窗DOM元素
        document.querySelectorAll('[role="dialog"]').forEach(el => {
          if (el.style.display !== 'none') el.remove();
        });
      }
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, [open]);

  return (
    {/* ... 省略弹窗主体代码 ... */}
  );
}

关键注意事项

  • Transition组件的unmount={true}是实现过渡后卸载的核心配置,必须开启
  • 处理输入框焦点可避免键盘唤起带来的DOM状态异常
  • 移动端视窗变化可能干扰HeadlessUI内部状态,需额外监听同步

内容的提问来源于stack exchange,提问作者Eric Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 04:35:28