【问题标题】:Replacing selected text on mouseup在 mouseup 上替换选定的文本
【发布时间】:2017-07-24 19:10:49
【问题描述】:

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

这里是小提琴: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>

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

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

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

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

问: 如何将我的 selection 替换为 selected 文本?

【问题讨论】:

  • 我建议您重新考虑一下交互是如何完成的。用户选择整个已选择的选择意味着什么?选择的一部分?等等,因为您最终可能会减少交互并简化场景。只是评论以防有帮助:)
  • 有趣...不知道如何进一步简化并保持功能...
  • 关闭....但没有雪茄:jsfiddle.net/4hd2vrex/2
  • jsfiddle.net/deep3015/5wmd2m21 尝试但未成功。这可能会给一些想法

标签: javascript jquery html css window


【解决方案1】:

我已经用我的方式完成了整个高亮文本,不是使用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>

【讨论】:

  • 很好的答案,我希望它可以帮助其他喜欢它的人帮助我!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-27
  • 2019-08-17
  • 2011-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多