带有多选和突出显示关键字的自定义下拉选择框(动态)

本文是在上一篇的基础上改造成 根据输入关键词动态筛选选项列表,然后实现多项选择并且关键词高亮。
上一篇:带有多选和突出显示关键字的自定义下拉选择框(静态) >>

带有多选和突出显示关键字的自定义下拉选择框:
Custom Dropdown Select Box with Multiple Selection and Highlighting Keywords:
不使用select元素,用div、ul、li、span元素实现带有多选和突出显示关键字的自定义下拉选择框。
完整控件包含三部分:

  1. 第一步用div模拟select选择框,ul+li模拟option选项,并用span标记关键词;
  2. 第二步CSS渲染展示效果;
  3. 第三步用原生JavaScript脚本实现点击选择,多选,取消选择,获取选中项结果(数组);
  4. 第四步改造成:根据输入关键词动态筛选选项列表。

文本中将多个关键词高亮显示为红色字体

我们常见的搜索引擎都会将检索出来的内容的关键词高亮显示为红色字体,那些一般是后端实现的。我们这里通过前端JavaScript脚本也可以实现:

<!-- 文本中将多个关键词高亮显示为红色字体 开始 -->
<script>
    let text = "这是一段包含多个关键词的文本,例如apple、banana和orange。";
    let keywords = "apple banana orange";
    // let keywords = ["apple", "banana", "orange"]; // 关键词列表,或者空格分隔多个关键词

    function highlightKeywords(text, keywords) {
        // 关键词输入形式多样性兼容
        if (Array.isArray(keywords)) {
            // 正常
        }
        else if (typeof keywords === 'string') {
            keywords = keywords.split(/\s+/); // 使用正则表达式来分割字符串,匹配一个或多个空格
        }
        else {
            throw new Error('keywords不是字符串也不是数组!');
        }

        // 遍历每个关键词
        keywords.forEach(keyword => {
            let regex = new RegExp(keyword, "gi");
            text = text.replace(regex, `<span style='color:red;'>${keyword}</span>`);
        });

        return text;
    }

    document.writeln(highlightKeywords(text, keywords)); // 模拟调用测试
</script>
<!-- 文本中将多个关键词高亮显示为红色字体 结束 -->

模拟动态筛选数据源

现代网页都是动态加载数据的,很少有纯静态网页了。在上一篇里面我们实现了静态的下拉框功能,在这里我们可以给他改成根据关键词动态筛选的。实际项目中一般使用异步加载功能(比如Ajax)调用后端api接口,加载筛选数据列表,然后绑定到选项里。

<!-- 模拟动态筛选数据源 JS脚本 开始 -->
<script>
    function filterData(keyword) {
        // 示例数组  
        const items = [
            { value: 1, text: '刘邦' },
            { value: 2, text: '刘备' },
            { value: 3, text: '刘秀' },
            { value: 4, text: '张无忌' },
            { value: 5, text: '魏无忌' },
            { value: 6, text: '何无忌' },
            { value: 7, text: '长孙无忌' }
        ];

        // 要搜索的关键词  
        keyword = keyword || 'apple';

        // 使用 filter() 方法查找匹配项  
        const matchedItems = items.filter(item => item.text.includes(keyword));
        // 输出匹配项  
        // console.log(matchedItems);
        return matchedItems;
    }

    filterData('apple'); //调用示例
</script>
<!-- 模拟动态筛选数据源 JS脚本 结束 -->

完整代码

完整代码在一个html文件里,您可以在本地新建文本文件,将完整代码复制到文件内容里面,然后重命名为: DropdownSelectBox-2.html ,双击即可在浏览器里看到效果。

完整代码:

点击查看代码
<!doctype html>
<html>

<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="MobileOptimized" content="240">
    <meta name="applicable-device" content="mobile">
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no,shrink-to-fit=no">
    <meta name="format-detection" content="telephone=no,email=no,adress=no">

    <!-- <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"> -->
    <!-- <link href="/favicon.ico" rel="icon" type="image/x-icon"> -->

    <!-- <link href="/Content/mobile/css/style.css?=202308082222" rel="stylesheet" type="text/css"> -->

    <!-- 自动加载适应移动端的css -->
    <!-- <link href="mobile.css" rel="stylesheet" type="text/css" media="screen and (max-device-width: 1080px)"> -->

    <meta name="theme-color" content="#3c80d4" media="(prefers-color-scheme: light)">
    <meta name="theme-color" content="#9cc8ff" media="(prefers-color-scheme: dark)">

    <title>带有多选和突出显示关键字的自定义下拉选择框</title>
    <meta name="Description"
        content="带有多选和突出显示关键字的自定义下拉选择框:Custom Dropdown Select Box with Multiple Selection and Highlighting Keywords">
    <meta name="author" content="熊仔其人">

    <!-- CSS样式 -->
    <style type="text/css">
        /* 自定义下拉选择框 CSS 开始 */
        .custom-select {
            position: relative;
            width: 200px;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 1px;
        }

        .selected-options {
            padding: 1px;
            cursor: pointer;
            background-color: #fff;
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
        }

        .selected-options .selectedOptions-icon-cross {
            background-color: #f8f9fa;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 1px;
        }

        .selected-options .selectedOptions-icon-cross::after {
            /* content: '\2717'; */
            content: '\2715';
        }

        .custom-select input[type="text"] {
            display: inline-flex;
            width: 196px;
            min-width: 1rem;
            max-width: 200px;
            border: none;
        }

        .placeholder {
            color: #999;
        }

        .options-list {
            position: absolute;
            top: 100%;
            left: 0;
            right: 0;
            border: 1px solid #ccc;
            border-top: none;
            border-radius: 0 0 5px 5px;
            background-color: #fff;
            list-style: none;
            padding: 0;
            margin: 0;
            display: none;
            /* 默认隐藏选项列表 */
        }

        .option {
            padding: 5px;
            cursor: pointer;
        }

        .option:hover {
            background-color: #f2f2f2;
        }

        .option.selected {
            background-color: #e0e0e0;
        }

        .option.selected::after {
            /* content: '\221A'; */
            content: '\2713';
            color: #007bff;
            margin-left: 5px;
        }

        .highlight {
            color: red;
            /* 关键词标红 */
        }

        /* 显示选项列表 */
        .custom-select.active .options-list {
            display: block;
        }

        /* 自定义下拉选择框 CSS 结束 */
    </style>

</head>

<body>
    带有多选和突出显示关键字的自定义下拉选择框:
    Custom Dropdown Select Box with Multiple Selection and Highlighting Keywords:
    <hr />

    <!-- 自定义下拉选择框 html 开始 -->
    <div class="custom-select">
        <div class="selected-options"></div>
        <input type="text" placeholder="关键词..." value="">
        <ul class="options-list">
            <li class="option" data-value="option1">选项1 <span class="highlight">关键词</span></li>
            <li class="option" data-value="option2">选项2</li>
            <li class="option" data-value="option3">这个选项3 也有<span class="highlight">关键词</span></li>
            <li class="option" data-value="option4">选项4</li>
        </ul>
    </div>
    <!-- 自定义下拉选择框 html 结束 -->



    <!-- 文本中将多个关键词高亮显示为红色字体 开始 -->
    <script>
        let text = "这是一段包含多个关键词的文本,例如apple、banana和orange。";
        let keywords = "apple banana orange";
        // let keywords = ["apple", "banana", "orange"]; // 关键词列表,或者空格分隔多个关键词

        function highlightKeywords(text, keywords) {
            // 关键词输入形式多样性兼容
            if (Array.isArray(keywords)) {
                // 正常
            }
            else if (typeof keywords === 'string') {
                keywords = keywords.split(/\s+/); // 使用正则表达式来分割字符串,匹配一个或多个空格
            }
            else {
                throw new Error('keywords不是字符串也不是数组!');
            }

            // 遍历每个关键词
            keywords.forEach(keyword => {
                let regex = new RegExp(keyword, "gi");
                text = text.replace(regex, `<span style='color:red;'>${keyword}</span>`);
            });

            return text;
        }

        // document.writeln(highlightKeywords(text, keywords));
    </script>
    <!-- 文本中将多个关键词高亮显示为红色字体 结束 -->

    <!-- 模拟动态筛选数据源 JS脚本 开始 -->
    <script>
        function filterData(keyword) {
            // 示例数组  
            const items = [
                { value: 1, text: '刘邦' },
                { value: 2, text: '刘备' },
                { value: 3, text: '刘秀' },
                { value: 4, text: '张无忌' },
                { value: 5, text: '魏无忌' },
                { value: 6, text: '何无忌' },
                { value: 7, text: '长孙无忌' }
            ];

            // 要搜索的关键词  
            keyword = keyword || 'apple';

            // 使用 filter() 方法查找匹配项  
            const matchedItems = items.filter(item => item.text.includes(keyword));
            // 输出匹配项  
            // console.log(matchedItems);
            return matchedItems;
        }

        // filterData('apple'); //调用示例
    </script>
    <!-- 模拟动态筛选数据源 JS脚本 结束 -->

    <!-- 自定义下拉选择框 JS脚本 开始 -->
    <script>
        document.addEventListener('DOMContentLoaded', function (event) {
            event.stopPropagation(); // 阻止事件冒泡到外层元素
            var customSelect;
            var inputDom = document.querySelector('.custom-select input[type="text"]');
            var optionsList;
            var options; // 选项(下拉选项列表里的)
            var selectedOptions = document.querySelector('.selected-options'); // 选中的选项(下拉选项列表里的)
            var selectedOptionResult; // 已选择的选项结果(显示在选择框里的)

            function getDom() {
                customSelect = document.querySelector('.custom-select');
                optionsList = document.querySelector('.options-list');
                options = document.querySelectorAll('.option');
                selectedOptionResult = document.querySelectorAll('.custom-select .selected-options .selectedOptions-icon-cross');
            }
            getDom();

            // 动态绑定选项点击事件
            function bindEventToOption(optionDom) {
                optionDom.addEventListener('click', function (event) {
                    event.stopPropagation(); // 阻止事件冒泡到外层元素
                    var isSelected = this.classList.contains('selected');
                    this.classList.toggle('selected', !isSelected);

                    // 更新已选项显示  
                    updateSelectedDisplay();
                });
            }

            // 使用addEventListener方法监听input事件。请注意,input事件与change事件不同。change事件在用户完成输入并且元素失去焦点时触发,或者在<select>元素中选择了一个选项时触发。而input事件则会在用户每次输入或修改值时立即触发。
            // input事件非常适用于需要即时响应用户输入的场景,比如实时搜索、输入验证等
            inputDom.addEventListener('input', function (event) {
                event.stopPropagation(); // 阻止事件冒泡到外层元素
                // event.target指向触发事件的元素,即inputElement  
                // 通过event.target.value可以获取到当前输入的值  
                // console.log('输入值已变化:', event.target.value);

                // 即时响应用户输入的场景,比如实时搜索
                var kwArray = filterData(event.target.value);
                optionsList.innerHTML = '';
                getDom();
                kwArray.forEach(function (item) {
                    var li = document.createElement('li');
                    li.setAttribute('class', 'option');
                    li.setAttribute('data-value', item.value);
                    // li.textContent = item.text + ' ';
                    li.innerHTML = highlightKeywords(item.text + ' ', event.target.value); //包含关键词高亮红色显示

                    // 搜索框里已选中项,并且不在当前选项列表里,要留存
                    selectedOptionResult.forEach(function (option) {
                        var optionItem = { value: option.getAttribute('data-value'), text: option.textContent.trim() };
                        // 当前选项列表中的选项
                        if (item.value == optionItem.value) {
                            li.classList.toggle('selected', true);// 设置为选中状态
                        }
                    });

                    bindEventToOption(li);
                    optionsList.appendChild(li);
                });
                customSelect.classList.add('active');
                getDom();

            });


            // 显示/隐藏选项列表  
            selectedOptions.addEventListener('click', function (event) {
                event.stopPropagation(); // 阻止事件冒泡到外层元素
                customSelect.classList.toggle('active');
            });

            options.forEach(function (option) {
                bindEventToOption(option); // 选项点击事件
            });

            // 点击选项以外的地方关闭选项列表  
            document.addEventListener('click', function (event) {
                event.stopPropagation(); // 阻止事件冒泡到外层元素
                if (!customSelect.contains(event.target)) {
                    customSelect.classList.remove('active');
                }
            });

            // 更新已选项的显示  
            function updateSelectedDisplay() {
                var selectedItems = [];
                getDom();

                // 搜索框里已选中项,并且不在当前选项列表里,要留存
                selectedOptionResult.forEach(function (option) {
                    var optionItem = { value: option.getAttribute('data-value'), text: option.textContent.trim() };
                    // 当前选项列表中的选项
                    var optionItemB = [];
                    options.forEach(function (optionB) {
                        optionItemB.push({ value: optionB.getAttribute('data-value'), text: optionB.textContent.trim() });
                    });
                    if (!optionItemB.some(x => x.value == optionItem.value)) {
                        selectedItems.push(optionItem);
                    }
                });

                // 当前选项列表中的选中项
                options.forEach(function (option) {
                    var optionItem = { value: option.getAttribute('data-value'), text: option.textContent.trim() };
                    if (option.classList.contains('selected') && !selectedItems.some(x => x.value == optionItem.value)) {
                        selectedItems.push(optionItem);
                    }
                });

                // 清空并重新填充已选项  
                selectedOptions.innerHTML = '';
                if (selectedItems.length === 0) {
                    // selectedOptions.appendChild(document.createTextNode('请选择...'));
                } else {
                    selectedItems.forEach(function (item) {
                        var span = document.createElement('span');
                        span.setAttribute('class', 'selectedOptions-icon-cross');
                        span.setAttribute('data-value', item.value);
                        span.textContent = item.text + ' ';
                        span.addEventListener('click', function (event) {
                            event.stopPropagation(); // 阻止事件冒泡到外层元素
                            var itemValue = this.getAttribute('data-value');
                            options.forEach(function (option) {
                                if (option.getAttribute('data-value') == itemValue) {
                                    option.classList.toggle('selected', false);
                                }
                            });

                            // 更新已选项显示  
                            updateSelectedDisplay();
                        });
                        selectedOptions.appendChild(span);
                    });
                }
            }
        });

        // 获取选中项
        function getSelectedItems() {
            var selectedItems = [];
            var selectedOptions = document.querySelectorAll('.custom-select .selected-options .selectedOptions-icon-cross');
            selectedOptions.forEach(function (option) {
                selectedItems.push({ value: option.getAttribute('data-value'), text: option.textContent.trim() });
            });

            return selectedItems;
        }

    </script>
    <!-- 自定义下拉选择框 JS脚本 结束 -->


</body>

</html>

效果

效果展示:

实际项目中一般使用异步加载功能(比如Ajax)调用后端api接口,加载筛选数据列表,然后绑定到选项里。
【完】

posted @ 2024-12-02 16:33  熊仔其人  阅读(89)  评论(0编辑  收藏  举报