User-select: none 在 Safari 中的行为不同

User-select: none behaves differently in Safari

我想要达到的目标

我正在构建类似输入的可编辑内容 div。您应该单击 div 外的一些标签以将它们添加到 div 内,同时还可以在所述标签周围输入。

问题及重现方法

我正在使用 user-select: none(普通和 webkit)来防止标记按钮被 selected,因此失去了插入符号的位置。它适用于 Firefox 和 Chrome 但不适用于 Safari(我知道 -webkit- 前缀并使用它)。

Here is a fiddle where you can reproduce the problem.

我试过的

我的问题的根源是保持插入符号的位置,同时让内容保持可编辑状态 div。

我之前曾尝试使用 rangy,但遇到了有关 Firefox 的一些限制。从用户体验的角度来看,这些限制非常烦人。您可以查看我之前的问题以及它是如何让我来到这里的,这个用户-select:none 解决方案-

这就是我如何得到这个以用户-select为特色的解决方案:none.

我的components/JS:

new Vue({
  el: "#app",
        data(){
            return {
                filters_toggled: false,
                fake_input_content: '',
                input_length: 0,
                typed: false,
                boolean_buttons: [{
                    type: '1',
                    label: 'ȘI',
                    tag: 'ȘI',
                    img: 'https://i.imgur.com/feHin0S.png'
                }, {
                    type: '2',
                    label: 'SAU',
                    tag: 'SAU',
                    img: 'https://i.imgur.com/vWJeJwb.png'
                }, {
                    type: '3',
                    label: 'NU',
                    tag: 'NU',
                    img: 'https://i.imgur.com/NNg1spZ.png'
                }],
                saved_sel: 0,
                value: null,
                options: ['list', 'of', 'options']
            }
        },
        name: 'boolean-input',
        methods: {
            inputLength($event){
                this.input_length = $event.target.innerText.length;
                if(this.input_length == 0)
                    this.typed = false;
            },
            addPlaceholder(){
                if(this.input_length == 0 && this.typed == false){
                    this.$refs.divInput.innerHTML = 'Cuvinte cheie, cautare booleana..'
                }
            },
            clearPlaceholder(){
                if(this.input_length == 0 && this.typed == false){
                    this.$refs.divInput.innerHTML = '';
                }
            },
            updateBooleanInput($event){
                this.typed = true;
                this.inputLength($event);
            },
            saveCursorLocation($event){
        /*
                if($event.which != 8){
                    if(this.saved_sel)
                        rangy.removeMarkers(this.saved_sel)
                    this.saved_sel = rangy.saveSelection();
                }
                */
                // if(this.input_length == 0 && this.typed == false){
                //  var div = this.$refs.divInput;
                //  var sel = rangy.getSelection();
                //  sel.collapse(div, 0);
                // }
            },
            insertNode: function(node){
                var selection = rangy.getSelection();
                var range = selection.getRangeAt(0);
                range.insertNode(node);
                range.setStartAfter(node);
                range.setEndAfter(node);
                selection.removeAllRanges();
                selection.addRange(range);
            },
            addBooleanTag($event){
                // return this.$refs.ChatInput.insertEmoji($event.img);
                if (!this.$refs.divInput.contains(document.activeElement)) {
                    this.$refs.divInput.focus();
                }

                console.log(this.input_length);
                if(this.typed == false & this.input_length == 0){
                    this.$refs.divInput.innerHTML = ''
                    var space = '';
                    this.typed = true
                    //this.saveCursorLocation($event);
                }
                //rangy.restoreSelection(this.saved_sel);
        console.log(getSelection().anchorNode, getSelection().anchorOffset, getSelection().focusNode, getSelection().focusOffset)

                var node = document.createElement('img');
                node.src = $event.img;
                node.className = "boolean-button--img boolean-button--no-margin";
                node.addEventListener('click', (event) => {
                    // event.currentTarget.node.setAttribute('contenteditable','false');
                    this.$refs.divInput.removeChild(node);
                })
                this.insertNode(node);
                this.saveCursorLocation($event);
            },
            clearHtmlElem($event){
                var i = 0;
                var temp = $event.target.querySelectorAll("span, br");
                if(temp.length > 0){
                    for(i = 0; i < temp.length; i++){
                        if(!temp[i].classList.contains('rangySelectionBoundary')){
                            if (temp[i].tagName == "br"){
                                temp[i].parentNode.removeChild(temp[i]);
                            } else {
                                temp[i].outerHTML = temp[i].innerHTML;
                            }
                        }
                    }
                }
            },
            pasted($event){
                $event.preventDefault();
                var text = $event.clipboardData.getData('text/plain');
                this.insert(document.createTextNode(text));
                this.inputLength($event);
                this.typed == true;
            },
            insert(node){
                this.$refs.divInput.focus();
                this.insertNode(node);
                this.saveCursorLocation($event);
            },
            fixDelete(){

            }
        },
        props: [ 'first'],
        mounted() {
            this.addPlaceholder()
        }
})

我的HTML

<div id="app">
        <div class="input__label-wrap">
            <span class="input__label">Cauta</span>
            <div style="user-select: none; -webkit-user-select: none">
                <span readonly v-on:click="addBooleanTag(b_button)" v-for="b_button in boolean_buttons" class="boolean-buttons">{{b_button.label}}</span>
            </div>
        </div> 
        <div class="input__boolean input__boolean--no-focus">
            <div 
                    @keydown.enter.prevent
                    @blur="addPlaceholder"
                    @keyup="saveCursorLocation($event); fixDelete(); clearHtmlElem($event);"
                    @input="updateBooleanInput($event); clearHtmlElem($event);"
                    @paste="pasted"
                    v-on:click="clearPlaceholder(); saveCursorLocation($event);"
                    class="input__boolean-content"
                    ref="divInput"
                    contenteditable="true">Cuvinte cheie, cautare booleana..</div>
        </div>
</div>

我的CSS

    .filters__toggler
    {
        cursor: pointer;
        padding: 2px;
        transition: all 0.2s ease-in-out;
        margin-left: 10px;
    }

        .filters__toggler path
        {
            fill: #314964;
        }

    .filters__toggler-collapsed
    {
        transform: rotate(-180deg);
    }

    .input__label
    {
        font-family: $roboto;
        font-size: 14px;
        color: #314964;
        letter-spacing: -0.13px;
        text-align: justify;
    }

    .input__boolean
    {
        width: 100%;
        background: #FFFFFF;
        border: 1px solid #AFB0C3;
        border-radius: 5px;
        padding: 7px 15px 7px;
        font-family: $opensans;
        font-size: 14px;
        color: #082341;
        min-height: 40px;
        box-sizing: border-box;
        margin-top: 15px;
        display: flex;
        flex-direction: row;
        align-items: center;
        line-height: 22px;
        overflow: hidden;
    }

        .input__boolean-content
        {
            width: 100%;
            height: 100%;
            outline: none;
            border: none;
            position: relative;
            padding: 0px;
            word-break: break-word;
        }

        .input__boolean img
        {
            cursor: pointer;
            margin-bottom: -6px;
        }

    .input__boolean--no-focus
    {
        color: #9A9AA6
    }

.input__label-wrap
{
    display: flex;
    justify-content: space-between;
    width: 100%;
    position: relative;
}

    .boolean-buttons
    {
        background-color: #007AFF;
        padding: 3px 15px;
        border-radius: 50px;
        color: #fff;
        font-family: $roboto;
        font-size: 14px;
        font-weight: 300;
        cursor: pointer;
        margin-left: 10px;
    }

        .boolean-button--img
        {
            height: 22px;
            margin-left: 10px;
        }

        .boolean-button--no-margin
        {
            margin: 0;
        }

.popper
{
    background-color: $darkbg;
    font-family: $opensans;
    font-size: 12px;
    line-height: 14px;
    color: #fff;
    padding: 4px 12px;
    border-color: $darkbg;
    box-shadow: 0 5px 12px 0 rgba(49,73,100,0.14);
    border-radius: 8px;
}

.filters__helper
{
    cursor: pointer;
    margin-left: 10px;
    margin-bottom: -3px;
}

.popper[x-placement^="top"] .popper__arrow
{
    border-color: #082341 transparent transparent transparent;
}

注意:忽略新的 vue,它是从 Fiddle 粘贴的。我建议使用 fiddle 检查代码,重现问题。

预期行为与实际结果

在 Safari(最新版本)中,如果我键入一个词然后单击该词中的某处或通过键盘箭头移动该词中的插入符号然后单击输入右侧的标签之一,标签应该添加在点击单词的中间(selection 在哪里制作)但它被添加在单词的开头。

tl;dr:单击其中一个标签时,Safari 不遵守插入符号的位置。它在内容可编辑 div 的开头添加标签,而不是之前插入符号所在的位置。

编辑 1:根据这些日志,getSelection() 告诉我们偏移量始终为 0,因为在 Safari 中,div 失去焦点。

看来你基本上已经自己找到了答案。这是时间问题。

如果将事件更改为 mousedown,则插入符号位置不会丢失,标签会插入到正确位置。

<div id="app">
  <div class="input__label-wrap">
   <span class="input__label">Cauta</span>
   <div style="user-select: none; -webkit-user-select: none">
    <span readonly v-on:mousedown="addBooleanTag(b_button)" v-for="b_button in boolean_buttons" class="boolean-buttons">{{b_button.label}}</span>
   </div>
  </div> 
  <div class="input__boolean input__boolean--no-focus">
   <div 
     @keydown.enter.prevent
     @blur="addPlaceholder"
     @keyup="saveCursorLocation($event); fixDelete(); clearHtmlElem($event);"
     @input="updateBooleanInput($event); clearHtmlElem($event);"
     @paste="pasted"
     v-on:click="clearPlaceholder(); saveCursorLocation($event);"
     class="input__boolean-content"
     ref="divInput"
     contenteditable="true">Cuvinte cheie, cautare booleana..</div>
  </div>
</div>

https://jsfiddle.net/xmuzp20o/

如果您不想在 mousedown 上添加实际标签,那么您至少可以在那个事件中保存插入符号位置,这样您在点击事件中仍然有正确的位置。