鼠标是否在元素中


鼠标是否在元素中

/*
* getBoundingClientRect
*
* getBoundingClientRect()方法用来获取页面中某个元素的左、上、右、下分别相对浏览器视窗(例如:元素
*
* 左边距离浏览器最左边的距离的位置,
*
* 返回的是一个矩形对象,包括四个属性,分别是left 、top、right、bottom。
*
* 分别表示元素各边与页面上边和左边的距离。
*/

// 组件中使用

/*
* 使用场景:
* 
* 现在有这样一个需求,
* 
* 鼠标在 是否在 antd switch 内, 决定了在接下来的 失焦事件中,是否调用 改变switch 开关的接口。
* 
* 在内调用,不在内不调用,组件卸载,移除键盘监听事件。
*
* 修改完input的值,鼠标单击页面任意位置都会触发失焦事件,
*
* 区别在于,是否位于一个switch 元素内。
* 
*
*/
/*
 * 外壳组件
 */

import React, {VFC, useCallback, useState, useEffect} from 'react';
import {Form, InputNumber, Row, Col} from 'antd';
import {getAllInfo, updateNum, updateById} from '@/service/search';
import Personalized from '@/components/Personalized';

import styles from './index.less';

interface KeywordsProps {
    content: string;
    id: number;
    position: number;
    status: number;
}

const {log} = console;

const layout = {
    labelCol: {span: 10},
    wrapperCol: {span: 14},
};

const Relevant: VFC = () => {
    const [keywordNum, setKeywordNum] = useState<number>(1);
    const [keywords, setKeywords] = useState<KeywordsProps[]>([]);
    const [form] = Form.useForm();

    const getAllInfoLocal = useCallback(async () => {
        const res = await getAllInfo();
        const {totalNum, searchDiscoveryList} = res.data;
        setKeywordNum(totalNum);
        setKeywords(searchDiscoveryList);
        form.setFieldsValue({searchNum: totalNum});
    }, [form]);

    const updateNumLocal = useCallback(
        async (num: number) => {
            await updateNum({content: String(num)});
            await getAllInfoLocal();
        },
        [getAllInfoLocal]
    );

    useEffect(() => {
        getAllInfoLocal();
    }, [getAllInfoLocal]);

    const onFinish = useCallback((values: any) => {
        log('Success:', values);
    }, []);

    const numChange = useCallback(
        e => {
            updateNumLocal(e);
        },
        [updateNumLocal]
    );

    return (
        <div className={styles.relevantWrap}>
            <Form {...layout} form={form} name='num' onFinish={onFinish} initialValues={{searchNum: keywordNum}}>
                <Form.Item
                    name='searchNum'
                    label='相关搜索数量设置'
                    extra='搜索数量1~10个'
                    rules={[{pattern: /^([1-9]|10)$/, message: '请输入1~10的数字'}]}
                >
                    <InputNumber min={1} max={10} onStep={e => numChange(e)} key={keywordNum} />
                </Form.Item>

                <Row style={{marginBottom: 20}}>
                    <Col offset={5} span={7}>
                        搜索发现位
                    </Col>
                    <Col span={12}>个性化推荐</Col>
                </Row>
                {keywords.map((item: KeywordsProps, index) => (
                    <Personalized form={form} index={index + 1} key={item.id} data={item} updateById={updateById} />
                ))}
            </Form>
        </div>
    );
};

export default Relevant;
/*
 * input switch 组合组件
 *
 * 这样循环出来的组件有多个,他们不会相互影响,即便是键盘监听事件也是如此。
 *
 * 而对于是否在内的判断,也只是针对对当前组件的当前 switch 相对于窗口上下左右位置的判断。
 *
 * 每一个这样的组件都会注册键盘监听事件,
 *
 * 在一定程度上来说,这的确重复了,
 *
 * 但在他们会跟着父级组件在组件卸载的时候一起销毁。
 * 
 * 确保不会在其他页面触发键盘事件,影响性能。
 * 
 */

import React, {FC, useEffect, useCallback, useState, useRef, MutableRefObject} from 'react';
import {Col, Form, FormInstance, Input, Row, Switch} from 'antd';
import _ from 'lodash';

import useSyncCallback from '@/hooks/useSyncCallback';

const layout = {
    labelCol: {span: 8},
    wrapperCol: {span: 16},
};
interface PersonalizedProps {
    form: FormInstance;
    index: number;
    data: {
        content: string;
        id: number;
        position: number;
        status: number;
    };
    updateById: (params: any) => void;
}

const Personalized: FC<PersonalizedProps> = props => {
    const {form, index, data, updateById} = props;
    const [isDis, setIsDis] = useState<boolean>(false);
    const [isOpen, setOpen] = useState(false);
    const switchRef: MutableRefObject<any> = useRef(null);

  // 双!是什么意思?
    useEffect(() => {
        setIsDis(!!data.status);
    }, [data.status]);

    const openIt = useCallback(
        async (e: any, index: number) => {
            if (form.getFieldsValue()[`words${index}`]) {
                setIsDis(e);
                  await updateById({...data, status: e ? 1 : 0, content:             form.getFieldValue(`words${index}`)});
            }
            setOpen(false);
        },
        [data, form, updateById]
    );

    const hasViewRange = (view: any, event: MouseEvent) => {
        if (view) {
            const wx = event.clientX;
            const wy = event.clientY;

            const {left, top, bottom, right} = view.getBoundingClientRect();
                        
              // 鼠标位于元素内,真空区域!
            if (wx >= left && wx <= right && wy >= top && wy <= bottom) return true;
            else return false;
        }
        return false;
    };

    const getSyncOpen = useSyncCallback(() => {
        isOpen && openIt(!isDis, index);
    });

    const onInputBlur = async () => {
        !!form.getFieldValue(`words${index}`) &&
            form.isFieldTouched(`words${index}`) &&
            (await updateById({...data, status: isDis ? 1 : 0, content: form.getFieldValue(`words${index}`)}));
        getSyncOpen(); // 使用同步函数,确保 state 最新值
    };

    const moveFunc = _.debounce((e: MouseEvent) => {
        // 若此刻鼠标位于 switch 区域内,hasViewRange 函数返回true
        console.warn('检测元素', hasViewRange(switchRef.current, e));
        setOpen(() => {
            if (hasViewRange(switchRef.current, e)) {
                return true;
            }
            return false;
        });
    }, 100); // 因为时间为 500 反应太慢,改成100
  
  // input 值变化,注册鼠标移动事件
    const handelChange = _.debounce(() => {
        addEventListener('mousemove', moveFunc);
    }, 500);

    useEffect(() => {
        return () => {
            removeEventListener('mousemove', moveFunc);
        };
    }, []);

    return (
        <div>
            <Row align='middle'>
                <Col span={12}>
                    <Form.Item
                        {...layout}
                        name={`words${index}`}
                        label={`位置${index}`}
                        rules={[{required: true, message: '请输入'}]}
                        initialValue={data.content}
                    >
                        <Input
                            disabled={isDis}
                            placeholder='请输入'
                            onBlur={() => onInputBlur()}
                            onChange={() => handelChange()}
                        />
                    </Form.Item>
                </Col>
                <Col span={11} offset={1}>
                    <Form.Item>
                        <Switch
                            ref={switchRef}
                            checkedChildren='开'
                            unCheckedChildren='关'
                            checked={isDis}
                            onChange={e => openIt(e, index)}
                        />
                    </Form.Item>
                </Col>
            </Row>
        </div>
    );
};

export default Personalized;
组件图片:

https://s3.bmp.ovh/imgs/2022/05/17/029b64903a16cb65.png

https://s3.bmp.ovh/imgs/2022/05/17/87dcded1c0920657.png

文章作者: KarlFranz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source KarlFranz !
评论
  目录