首页 > 解决方案 > 在 iOS <12 上无法以编程方式触发输入点击

问题描述

我在触发在旧 iOS 设备上打开文件对话框时遇到问题,具体来说,问题似乎出在仍在运行 iOS 12 的设备上。

我正在使用 React-Dropzone 包为文件创建一个放置区,但还添加了一种方法来标记该区域以打开文件对话框以选择文件。

然后我使用 Hammerjs 来检测 onTab 事件。

我可以通过添加一个在onTab被调用时触发的警报来确定onTab事件正在触发,并且它是旨在打开对话框的函数,而不是触发文件对话框在较旧的 iOS 设备上打开。

const FileUploadDropzone = () => {
...
        const {getRootProps, getInputProps, open, inputRef} = useDropzone({
            // Disable click and keydown behavior
            noClick: true,
            noKeyboard: true,
        });

        const handleTap = useCallback(() => {
            // specific function created by React-Dropzone to open the dialog
            open();
            // also tried to trigger the input click directly using a ref (have confirmted that the input is correctly referenced)
            inputRef.current.click();
        }, [allowInteract, uploading, open]);

        return (
            <Hammer onTap={handleTap}>
                <div {...getRootProps()}>
                    <input {...getInputProps()}/>
                    {children}
                </div>
            </Hammer>
        );
    };

根据我的阅读,输入不能将样式设置为display:none,如果是,则无法以编程方式触发打开文件对话框。

所以我设置了如下样式:

input {
    visibility: hidden;
    height: 0px;
    width: 0px;
  }

我还尝试做的是将输入和传递给组件的子元素包装在标签中,希望能够更好地支持单击标签打开对话​​框,但即使这样也行不通。

所以现在我很茫然,我不明白如何让它在这些旧的 iOS < 12 设备上工作。

标签: javascriptcssiosmobile-safarireact-dropzone

解决方案


该问题似乎与以下事实有关:当自定义单击事件与以编程方式调用输入单击事件之间存在任何延迟时,iOS 12 上的 Safari 不喜欢它。由于在我的例子中输入被深深地嵌套在 DOM 中,当事件的冒泡需要很长时间时会导致问题。

为了解决这个问题,我不得不创建一个 hack,将文件输入附加到 DOM 中,使其尽可能地像<body>.

我按照这篇文章利用 React.createPortal 将输入附加到组件之外。https://www.jayfreestone.com/writing/react-portals-with-hooks

<div>我创建了一个函数来在 React 应用程序的顶部底部创建一个空 div 。

function usePortal(cssSelector) {
    const rootElemRef = React.useRef(document.createElement('div'));

    useEffect(
        function setupElement() {
            const {current} = rootElemRef;
            const parentElem = document.querySelector(cssSelector);
            parentElem?.appendChild(current);
            return function removeElement() {
                current.remove();
            };
        },
        [id]
    );

    return rootElemRef.current;
}

然后创建了一个组件,该组件将为其子组件附加一个 custom div,由上面的函数创建

const Modal = ({children}) => {
    const target = usePortal('#react-root-div');
    return ReactDOM.createPortal(children, target);
};

然后简单地用 Modal 组件包装输入元素,它将(几乎)附加到 DOM 树的底部。

    <Modal>
        <input {...getInputProps()} />
    </Modal>

推荐阅读