javascript - 替换鼠标上选定的文本

标签 javascript jquery html css window

我正在构建一个实时 HTML 荧光笔,以便当用户选择一系列文本时,该文本将被具有背景属性的 span 元素包围。

这是 fiddle :https://jsfiddle.net/4hd2vrex/

问题是,当用户进行多次选择时,这可能会变得非常困惑,跨度被嵌套,我得到如下内容:

<span style="background-color: rgb(255, 255, 131);">
    r
    <span style="background-color: rgb(255, 255, 131);">
        <span style="background-color: rgb(255, 255, 131);">
            e
        </span>
        p
    </span>
    r
    <span style="background-color: rgb(255, 255, 131);">
        e
    </span>
    h
    <span style="background-color: rgb(255, 255, 131);">
        end
    </span>
    e
    <span style="background-color: rgb(255, 255, 131);">
        rit
    </span>
</span>

神圣的大奖 bat 侠!为了解决这个问题,我有以下想法:

在添加任何跨度之前,只需将所有选定的文本、跨度标签等全部替换为原始选定的文本window.getSelection()

例如,如果我选择了上面那堆乱七八糟的跨度,那么在用更多跨度包装所选文本之前,我会将这些跨度替换为 window.getSelection() ,这只是文本 reprehenderit 我会得到的。

<span style="background-color: rgb(255, 255, 131);">reprehenderit</span>

问: 如何用所选文本替换我的选择

最佳答案

我已经用我的方式完成了整个突出显示文本,没有使用 window.Selection API,而是使用:select(start,end).then(merge).then(filter).then(highlight)。最有趣的是它可以突出显示复杂的元素,即使只是文本。我发现 select api 还可以编写一个所见即所得的 html 编辑器,所以我将它分享给所有对选择问题感兴趣的人,并且希望对您有所帮助,好问题!

(function (context, factory) {
    if (typeof module != 'undefined' && typeof module.exports == 'object') {
        module.exports = factory(context);
    } else {
        factory(context, true);
    }
})(window || this, function (context, bind) {
    function promise(executor) {
        return new Promise(executor);
    }

    var $TYPE = 'nodeType', $TEXT = 'textContent', $PARENT = 'parentNode', $NEXT = 'nextSibling', $FIRST = 'firstChild', NIL = {};

    function leaf(node) {
        return node[$TYPE] == 3;
    }

    function next(node, tree) {
        var it = tree ? node[$FIRST] || node[$NEXT] : node[$NEXT];
        if (it) {
            if (leaf(it)) return it;
            return next(it, true);
        }
        var parent = node[$PARENT];
        return parent && next(parent);
    }

    function parent(node) {
        return node[$PARENT];
    }

    function wrap(node, start, end) {
        if (!node) throw 'node is null';
        if (!leaf(node)) throw 'node is not a leaf:' + node.tagName;
        var rawText = node[$TEXT];
        var rawLength = rawText.length;
        var self = {
            node: node,
            text: function (text) {
                if (text !== undefined) {
                    node.textContent = text;
                    return wrap(node, 0, text.length);
                }
                return rawText.substring(self.start(), self.end());
            },
            is: function (other) {
                return node == other.node;
            },
            start: function () {
                return start === NIL || !start ? 0 : start;
            },
            end: function () {
                return end === NIL || !end ? rawLength : end;
            },
            length: function () {
                return self.end() - self.start();
            },
            to: function (end) {
                return wrap(node, self.start(), end.end());
            },
            toLast: function () {
                return wrap(node, start, rawLength);
            },
            next: function () {
                var it = next(node);
                return it && wrap(it);
            },
            split: function () {
                if (self.length() >= rawLength) return self;
                var stack = [0].concat(self.start() || []).concat(self.end()).concat(self.end() != rawLength ? rawLength : []);
                var start = stack.shift();
                var separated = [];
                while (stack.length) {
                    var end = stack.shift();
                    var text = document.createTextNode(rawText.substring(start, end));
                    self.after(text);
                    separated.push(wrap(text));
                    start = end;
                }
                self.remove();
                return !self.start() ? separated[0] : separated[1];
            },
            remove: function (optimized) {
                var parent = node[$PARENT];
                if (optimized && parent.childNodes.length == 1) {
                    parent[$PARENT].removeChild(parent);
                }
                parent.removeChild(node);
                return this;
            },
            merge: function (other) {
                var it = self.split();
                return it.text(other.split().remove(true).text() + it.text());
            },
            after: function (e) {
                node[$PARENT].insertBefore(e, node);
                return this;
            },
            wrap: function (e) {
                e.appendChild(self.split().after(e).node);
            }
        };

        return self;
    }


    function select(start, end) {
        return promise(function (resolve) {
            start = wrap(start.text, start.offset, NIL), end = wrap(end.text, NIL, end.offset);
            var selected = [];
            while (start) {
                if (start.is(end)) {
                    selected.push(start.to(end));
                    break;
                }
                selected.push(start.toLast());
                start = start.next();
            }
            resolve(selected);
        });
    }

    function merge(filter) {
        return function (parts) {
            var result = [parts.shift()];
            while (parts.length) {
                var prev = result.pop();
                var next = parts.shift();
                if (filter(prev.node, next.node)) {
                    result.push(next.merge(prev));
                } else {
                    result.push(prev);
                    result.push(next);
                }
            }
            return result;
        }
    }

    function filter(test) {
        return function (parts) {
            return parts.filter(function (part) {
                return test(part.node);
            });
        }
    }

    function apply(consume) {
        return function (parts) {
            return parts.forEach(function (part) {
                return consume(part);
            });
        }
    }

    var exports = {
        __esModule: true,
        default: select,
        select: select,
        merge: merge,
        filter: filter,
        apply: apply
    };
    if (bind)for (var name in exports)context[name] = exports[name];
    return exports;
});


(function () {
    var COMPONENT_ID = 'highlight-' + +new Date;
    var highlighter = {
        init: function () {
            this.bindEvents();
        },
        /**
         *
         */
        bindEvents: function () {
            var self = this;
            $('.swatch').on('click', function () {
                $('.swatch').removeClass('active');
                $(this).addClass('active');
            });
            $('.content').mouseup(function () {
                var current = self.actived();
                if (current.hasClass('clear')) {
                    self.clear();
                } else {
                    self.highlight();
                }
            });

        },
        actived: function () {
            return $('.swatch.active');
        },
        color: function () {
            return this.actived().css('background-color');
        },
        /**
         *
         */
        highlight: function () {
            var self = this;
            var selection = self.getSelection();
            if (selection) {
                self.select(selection.getRangeAt(0)).//
                then(merge(function (left, right) {
                    var p1 = left.parentNode;
                    var p2 = right.parentNode;

                    var a1 = self.compare(left);
                    var a2 = self.compare(right);
                    return (a1 && a2 && p1.parentNode == p2.parentNode) ||
                        (!a1 && !a2 && p1 == p2) ||
                        (a1 && !a2 && p1.parentNode == p2) ||
                        (!a1 && a2 && p2.parentNode == p1);
                })).then(filter(function (part) {
                    return !self.compare(part);
                })).then(function (parts) {
                    parts.map(function (node) {
                        node.wrap(self.component());
                    });
                }).catch(function (e) {
                    console.log(e);
                });
                selection.removeAllRanges();
            }
        },
        component: function () {
            return $('<span data-toggle="' + COMPONENT_ID + '">').css('background-color', this.color()).get(0);
        },
        compare: function (text) {
            var self = this;
            var parent = $(text).parent();
            var highlighted = parent.is(self.selector());
            var color = parent.css('background-color');
            return highlighted && color == self.color();
        },
        selector: function () {
            return '[data-toggle="?"]'.replace(/\?/, COMPONENT_ID);
        },
        clear: function () {
            var self = this;
            var selection = self.getSelection();
            if (selection) {
                self.select(selection.getRangeAt(0)).then(apply(function (part) {
                    var text = $(part.split().node);
                    while (true) {
                        var comp = text.closest(self.selector());
                        if (!comp || !comp.length) {
                            break;
                        }
                        var children = comp.contents();
                        var first = children[0], last = children[children.length - 1];
                        if (text.is(last)) {
                            comp.after(text);
                        } else if (text.is(first)) {
                            comp.before(text);
                        } else {
                            var heading = comp.clone().empty();
                            for (var i = 0; i < children.length; i++) {
                                if (text.is(children[i])) {
                                    break;
                                }
                                heading.append(children[i]);
                            }
                            comp.before(heading).before(text);
                        }

                        if (first == last) comp.remove();
                    }
                }));
                selection.removeAllRanges();
            }
        },
        select: function (range) {
            return select(
                {text: range.startContainer, offset: range.startOffset},
                {text: range.endContainer, offset: range.endOffset}
            );
        },
        getSelection: function () {
            var sel = window.getSelection();
            return /^\s*$/.test(self && sel.toString()) ? null : sel;
        }
    };

    highlighter.init();

})();
body {
    margin: 0;
    background: #fafafa;
    box-shadow: 0 0 5rem rgba(0, 0, 0, 0.25) inset;
}

::-moz-selection {
    background-color: rgba(0, 0, 0, 0.2);
}

::selection {
    background-color: rgba(0, 0, 0, 0.2);
}

.content {
    padding: 100px;
}

.footer {
    padding: 0 100px 0 100px;
    flex-basis: 100%;
    height: 60px;
    background: #292B2C;
    position:fixed;top:0;width:100%;
}

.footer .items-left {
    float: left;
}

.footer-item {
    line-height: 60px;
}

#colors {
    padding: 12px;
}

.swatch {
    width: 30px;
    height: 30px;
    border-radius: 15px;
    box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.5), 0px 2px 2px rgba(0, 0, 0, 0.5);
    display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="content">
    <span style="color:red;"><b>Content</b> <i>Lorem</i> <font size='7'>ipsum</font> dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
    dolore magna aliqua. Ut enim ad minim</span> veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
    laborum.
    Content Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
    laborum.
    Content Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
    laborum.
    Content Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
    laborum.
</div>
<div class="footer">
    <div class="items-left">
        <div id="colors">
            <div class="swatch active" style="background-color: rgba(255,255,131,.5);"></div>
            <div class="swatch" style="background-color: rgba(255,140,218,.5);"></div>
            <div class="swatch" style="background-color: rgba(144,255,184,.5);"></div>
            <div class="swatch clear"></div>
        </div>
    </div>
</div>

关于javascript - 替换鼠标上选定的文本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42599812/

相关文章:

javascript - 单击后如何隐藏按钮

jquery - 将不同的图像更改/裁剪为动态调整大小(根据浏览器的)div

jquery - 想要使用 Jquery 在屏幕上/屏幕外翻译 100% 宽度和高度的图像

javascript - HTML <select> 元素是否可以在不运行页面范围的 javascript 的情况下将默认的 selectedIndex 设置为 -1?

javascript - 当项目被选择或输入与列表中的项目匹配时,HTML5 数据列表的 jQuery 事件

javascript - 从用户系统复制文件

javascript - 如何在dynatree中选择节点

php - 从 jquery 插件中的多选组获取值

php - 我如何在提交时显示错误并在页面上保留 DIV(特殊框)

javascript - 如何 "fluent API"(也调用 "chaining")?