web-dev-qa-db-fra.com

entrée monoligne modifiable

Pour une application que nous développons dans l'entreprise où je travaille, nous avons besoin d'une entrée qui prend en charge l'insertion d'émoticônes dans notre application Web basée sur JS. Nous utilisons actuellement une entrée avec les codes courts des émoticônes (c'est-à-dire ':-)') et nous souhaitons passer à l'insertion d'images graphiques réelles.

Notre plan initial était d'utiliser un contenteditable<div>. Nous utilisons des écouteurs pour l'événement de collage ainsi que les différentes interactions clé/souris pour garantir qu'aucun balisage indésirable n'entre dans le contenu modifiable (nous supprimons le texte de ses balises de conteneur et ne laissons que des balises d'image que nous avons insérées nous-mêmes).

Cependant, le problème en ce moment est que le div redimensionne si vous mettez suffisamment de contenu (c'est-à-dire que sa hauteur augmente). Nous ne voulons pas que cela se produise et il n'est pas acceptable que le texte soit simplement caché (c'est-à-dire plain overflow: hidden). Donc:

Existe-t-il un moyen de faire en sorte que le div contentable se comporte comme une entrée sur une seule ligne?

Je préfère qu'il y ait une propriété d'attribut/css relativement simple que j'ai ratée qui fera ce que je veux, mais si nécessaire, les suggestions CSS + JS seront également appréciées.

53
Gijs
[contenteditable="true"].single-line {
    white-space: nowrap;
    width:200px;
    overflow: hidden;
} 
[contenteditable="true"].single-line br {
    display:none;

}
[contenteditable="true"].single-line * {
    display:inline;
    white-space:nowrap;
}
<div contenteditable="true" class="single-line">
    This should work.
</div>​
103
Alessio

Je pense que vous recherchez un contenteditablediv avec une seule ligne de texte qui défile horizontalement quand il déborde du div. Cela devrait faire l'affaire: http://jsfiddle.net/F6C9T/1

div {
    font-family: Arial;
    font-size: 18px;
    min-height: 40px;
    width: 300px;
    border: 1px solid red;
    overflow: hidden;
    white-space: nowrap;
}
<div contenteditable>
    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
</div>

Le min-height: 40px incorpore la hauteur à laquelle la barre de défilement horizontale apparaît. UNE min-height:20px se développerait automatiquement lorsque la barre de défilement horizontale apparaît, mais cela ne fonctionne pas dans IE7 (bien que vous puissiez utiliser des commentaires conditionnels pour appliquer un style séparé si vous le souhaitez).

6
tw16

Voici une solution relativement simple qui utilise l'événement d'entrée de contenteditable pour analyser le dom et filtrer diverses saveurs de nouvelles lignes (il devrait donc être robuste contre le copier/coller, le glisser-déposer, la frappe sur le clavier, etc.). Il condense plusieurs TextNodes en un seul TextNodes, supprime les retours à la ligne des TextNodes, tue les BR et place un "display: inline" sur tout autre élément qu'il touche. Testé sur Chrome, aucune garantie ailleurs.

var editable = $('#editable')

editable.on('input', function() {
  return filter_newlines(editable);
});


function filter_newlines(div) {
    var node, prev, _i, _len, _ref, _results;
    prev = null;
    _ref = div.contents();
    _results = [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        node = _ref[_i];
        if (node.nodeType === 3) {
            node.nodeValue = node.nodeValue.replace('\n', '');
            if (prev) {
                node.nodeValue = prev.nodeValue + node.nodeValue;
                $(prev).remove();
            }
            _results.Push(prev = node);
        } else if (node.tagName.toLowerCase() === 'br') {
            _results.Push($(node).remove());
        } else {
            $(node).css('display', 'inline');
            filter_newlines($(node));
            _results.Push(prev = null);
        }
    }
    return _results;
}
#editable {
    width: 200px;
    height: 20px;
    border: 1px solid black;
}
<div id="editable" contenteditable="true"></div>

Ou voici le violon: http://jsfiddle.net/tG9Qa/

3
josh

Si vous voulez une autre façon de le résoudre que de changer les exigences, avec un peu de display:table c'est tout à fait possible =)

.container1 {
    height:20px;
    width:273px; 
    overflow:hidden;
    border:1px solid green;
}
.container2 {
    display:table;
}
.textarea {
    width:273px;
    font-size: 18px;
    font-weight: normal;
    line-height: 18px;
    outline: none;
    display: table-cell;
    position: relative;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
    user-select: text;
    Word-wrap: break-Word;    
    overflow:hidden;
}
<div class="container1">
    <div class="container2">
        <div contenteditable="true" class="textarea"></div>
    </div>
</div>
2
Berge Aadland

Donc, pour la postérité: la solution la plus simple consiste à faire en sorte que votre chef de produit modifie les exigences afin que vous puissiez effectuer une édition multiligne. C'est ce qui s'est finalement produit dans notre cas.

Cependant, avant que cela ne se produise, j'ai fini par faire du chemin dans la création d'un éditeur de texte enrichi d'une seule ligne à déplacement manuel. Je l'ai enveloppé dans un plugin jQuery à la fin. Je n'ai pas le temps de le terminer (il y a probablement des bogues dans IE, Firefox fonctionne mieux et Chrome fonctionne plutôt bien - les commentaires sont clairsemés et parfois pas très clairs). Il utilise des parties de la bibliothèque Rangy (extraite parce que je ne voulais pas compter sur la bibliothèque complète) pour obtenir les positions d'écran des sélections afin de tester la position de la souris par rapport à la sélection (afin que vous puissiez faire glisser les sélections et déplacer la boîte).

En gros, cela fonctionne en utilisant 3 éléments. Un div externe (la chose sur laquelle vous appelez le plugin), qui se déborde: caché, puis 2 niveaux d'imbrication à l'intérieur. Le premier niveau est absolument positionné, le deuxième niveau est le contenu réel modifiable. Cette séparation est nécessaire car dans le cas contraire certains navigateurs donneront à l'élément contenté des pinces absolument positionnées, pour permettre à l'utilisateur de le déplacer ...

Dans tous les cas, il y a tout un tas de code pour déplacer l'élément absolument positionné autour de l'intérieur de l'élément supérieur, déplaçant ainsi le contenu réel modifiable. Le contenteditable lui-même a maintenant un espace blanc, etc. etc. pour le forcer à rester une seule ligne.

Il y a aussi du code dans le plugin qui supprime tout ce qui n'est pas une image (comme br, tableaux, etc. etc.) de tout texte collé/glissé dans la boîte. Vous avez besoin de certaines parties de cela (comme les brs, les paragraphes de suppression/normalisation, etc.) mais peut-être voudriez-vous normalement conserver les liens, em, strong et/ou une autre mise en forme.

Source: https://Gist.github.com/1161922

1
Gijs

Les autres réponses sont fausses et contiennent peu d'erreurs (le 2019-05-07). D'autres solutions suggèrent d'utiliser "white-space: nowrap" (empêche le transport vers une autre ligne) + "overflow: hidden" (empêche le long texte d'aller au-delà du champ) + masquer <br> et autres.

La première erreur en ce sens que les solutions sont "débordement: caché" empêche également le défilement du texte. L'utilisateur ne pourra pas faire défiler le texte en:

  • Appuyez sur le bouton central de la souris
  • Sélection du texte et déplacement du pointeur de la souris vers la gauche ou la droite
  • Utilisation du défilement horizontal de la souris (lorsque l'utilisateur a une telle chose)

La seule façon dont il peut faire défiler est d'utiliser les flèches du clavier.

Vous pouvez résoudre ce problème en utilisant "overflow: hidden" et "overflow: auto" (ou "scroll") en même temps. Vous devez créer un div parent avec "overflow: hidden" pour masquer le contenu que l'utilisateur ne devrait pas voir. Cet élément doit avoir des bordures d'entrée et un autre design. Et vous devez créer un div enfant avec l'attribut "overflow-x: auto" et "contenteditable". Cet élément aura une barre de défilement afin que l'utilisateur puisse le faire défiler sans aucune limitation et il ne verra pas cette barre de défilement en raison du masquage du débordement dans l'élément parent.

Exemple de solution:

document.querySelectorAll('.CETextInput').forEach(el => {
        //Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
        el.parentNode.addEventListener('mousedown', function(e) {
                if (e.target === this) {
                        setTimeout(() => this.children[0].focus(), 0);
                }
        });
        
        //Prevent Enter. See purpose in "Step 2" in answer.
        el.parentNode.addEventListener('keydown', function(e) {
                if (e.keyCode === 13)
                        e.preventDefault();
        });
});
.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
        display: inline-block;
        border: 1px solid #aaa;
}

.CETextInputCont {
        overflow: hidden;
        cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
        
        /*Style:*/
        width: 10em;
        height: 1em;
        line-height: 1em;
        padding: 5px;
        font-size: 20px;
        font-family: sans-serif;
}

.CETextInput {
        white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
        overflow-x: auto;
        min-height: 100%; /*to prevent zero-height with no text*/
        
        /*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
        padding: 5px 0;
        margin-top: -5px;
        
        outline: none; /*Prevent border on focus in some browsers*/
}
<div class="CETextInputBorder">
        <div class="CETextInputCont">
                <div class="CETextInput" contenteditable></div>
        </div>
</div>


Étape 2: Résolution du problème avec <br> et autres:

Il y a aussi un problème que l'utilisateur ou les extensions peuvent coller

  • <br> (peut être collé par l'utilisateur)
  • <img> (peut avoir une grande taille) (peut être collé par l'utilisateur)
  • éléments avec une autre valeur "d'espace blanc"
  • <div> et d'autres éléments qui portent du texte sur une autre ligne
  • éléments avec une valeur "d'affichage" inappropriée

Mais conseille de cacher tout <br> a tort aussi. C'est parce que Mozilla Firefox ajoute un élément <br> au champ vide (je suppose que cela peut être une solution de bug avec le curseur de texte disparaissant après la suppression du dernier caractère; vérifié dans Firefox 66 publié le 2019-03-19). Si vous masquez cet élément, lorsque l'utilisateur déplace le focus sur le champ, le curseur est défini dans cet élément <br> caché et le curseur de texte est également masqué (toujours).

Vous pouvez résoudre ce problème si vous serez <br> lorsque vous savez que le champ est vide. Vous avez besoin d'un peu de javascript ici (vous ne pouvez pas utiliser: sélecteur vide car le champ contient <br> éléments et n'est pas vide). Exemple de solution:

document.querySelectorAll('.CETextInput').forEach(el => {
        //OLD CODE:
        
        //Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
        el.parentNode.addEventListener('mousedown', function(e) {
                if (e.target === this) {
                        setTimeout(() => this.children[0].focus(), 0);
                }
        });
        
        //Prevent Enter to prevent blur on Enter
        el.parentNode.addEventListener('keydown', function(e) {
                if (e.keyCode === 13)
                        e.preventDefault();
        });
        
        //NEW CODE:
        
        //Update "empty" class on all "CETextInput" elements:
        updateEmpty.call(el); //init
        el.addEventListener('input', updateEmpty);

        function updateEmpty(e) {
                const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
                this.classList.toggle('empty', !s);
        }
});
/*OLD CODE:*/

.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
        display: inline-block;
        border: 1px solid #aaa;
}

.CETextInputCont {
        overflow: hidden;
        cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
        
        /*Style:*/
        width: 10em;
        height: 1em;
        line-height: 1em;
        padding: 5px;
        font-size: 20px;
        font-family: sans-serif;
}

.CETextInput {
        white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
        overflow-x: auto;
        min-height: 100%; /*to prevent zero-height with no text*/
        
        /*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
        padding: 5px 0;
        margin-top: -5px;
        
        outline: none; /*Prevent border on focus in some browsers*/
}

/*NEW CODE:*/

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
        display: none;
}

.CETextInput * {
        display: inline;
        white-space: pre;
}
<!--OLD CODE:-->

<div class="CETextInputBorder">
        <div class="CETextInputCont">
                <div class="CETextInput" contenteditable></div>
        </div>
</div>


Étape 3: Résolution du problème d'obtention de valeur:

Nous avons caché <br> des éléments pour que la valeur "innerText" ne les contienne pas. Mais:

  1. Lorsque la classe "vide" est définie, le résultat peut contenir <br> des éléments.
  2. Vos autres styles ou extensions peuvent remplacer "display: none" par la marque "! Important" ou par une règle de priorité plus élevée.

Ainsi, lorsque vous obtenez de la valeur, vous devez effectuer un remplacement pour éviter d'obtenir des sauts de ligne accidentels:

s = s.replace(/[\r\n]+/g, '');


N'utilisez pas javascript pour masquer <br>

Vous pouvez également résoudre le problème avec <br> en les supprimant par javascript mais c'est une très mauvaise solution car après chaque suppression, l'utilisateur ne peut plus utiliser l'action "annuler" pour annuler les modifications avant la suppression.

Vous pouvez également utiliser document.execCommand ('supprimer') pour supprimer <br> mais il est difficile à mettre en œuvre + l'utilisateur peut annuler votre suppression et restaurer les éléments <br>.


Ajout d'un espace réservé

Elle n'a pas été posée en question, mais je suppose que de nombreuses personnes utilisant des éléments modifiables sur une seule ligne en auront besoin. Voici un exemple de création d'espace réservé à l'aide de css et de la classe "vide" dont nous avons parlé plus haut:

//OLD CODE:

document.querySelectorAll('.CETextInput').forEach(el => {
        //Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
        el.parentNode.addEventListener('mousedown', function(e) {
                if (e.target === this) {
                        setTimeout(() => this.children[0].focus(), 0);
                }
        });
        
        //Prevent Enter to prevent blur on Enter
        el.parentNode.addEventListener('keydown', function(e) {
                if (e.keyCode === 13)
                        e.preventDefault();
        });
        
        //Update "empty" class on all "CETextInput" elements:
        updateEmpty.call(el); //init
        el.addEventListener('input', updateEmpty);

        function updateEmpty(e) {
                const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
                this.classList.toggle('empty', !s);
                
                //NEW CODE:
                
                //Make element always have <br>. See description in html. I guess it is not needed because only Firefox has bug with bad cursor position but Firefox always adds this element by itself except on init. But on init we are adding it by ourselves (see html).
                if (!s && !Array.prototype.filter.call(this.children, el => el.nodeName === 'BR').length)
                        this.appendChild(document.createElement('br'));
        }
});
/*OLD CODE:*/

.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
        display: inline-block;
        border: 1px solid #aaa;
}

.CETextInputCont {
        overflow: hidden;
        cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
        
        /*Style:*/
        width: 10em;
        height: 1em;
        line-height: 1em;
        padding: 5px;
        font-size: 20px;
        font-family: sans-serif;
}

.CETextInput {
        white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
        overflow-x: auto;
        min-height: 100%; /*to prevent zero-height with no text*/
        
        /*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
        padding: 5px 0;
        margin-top: -5px;
        
        outline: none; /*Prevent border on focus in some browsers*/
}

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
        display: none;
}

.CETextInput * {
        display: inline;
        white-space: pre;
}

/*NEW CODE:*/

.CETextInput[placeholder].empty::before { /*Use ::before not ::after or you will have problems width first <br>*/
        content: attr(placeholder);
        display: inline-block;
        width: 0;
        white-space: nowrap;
        pointer-events: none;
        cursor: text;
        color: #b7b7b7;
        
        padding-top: 8px;
        margin-top: -8px;
}
<!--OLD CODE:-->

<div class="CETextInputBorder">
        <div class="CETextInputCont">
                <div class="CETextInput" placeholder="Type something here" contenteditable><br></div>
        </div>
</div>

<!--We manually added <br> element for Firefox browser because Firefox (tested on 2019-05-11, Firefox 66) has bug with bad text cursor position in empty contenteditable elements that have ::before or ::after pseudo-elements.-->


Solution avec un seul div et "scrollbar-width"

Vous pouvez également utiliser une seule div en définissant "overflow-x: auto", "overflow-y: hidden" et "scrollbar-width: none". Mais "scrollbar-width" est une nouvelle propriété et ne fonctionne que dans Firefox 64+ et aucun autre navigateur pour le moment.

Vous pouvez également ajouter:

  • version avec préfixe webkit: "-webkit-scrollbar-width: none"
  • non standardisé ".CETextInput :: - webkit-scrollbar {display: none;}" (pour les navigateurs basés sur webkit)
  • "-ms-overflow-style: aucun"

Je ne recommanderais pas d'utiliser cette solution, mais voici un exemple:

//OLD CODE:

document.querySelectorAll('.CETextInput').forEach(el => {
        //Focusing on child is not needed anymore
        
        //Prevent Enter to prevent blur on Enter
        el.addEventListener('keydown', function(e) {
                if (e.keyCode === 13)
                        e.preventDefault();
        });
        
        //Update "empty" class on all "CETextInput" elements:
        updateEmpty.call(el); //init
        el.addEventListener('input', updateEmpty);

        function updateEmpty(e) {
                const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
                this.classList.toggle('empty', !s);
        }
});
/*NEW CODE:*/

.CETextInput {
        white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
        overflow-x: auto; /*or "scroll"*/
        overflow-y: hidden;
        -webkit-scrollbar-width: none; /*Chrome 4+ (probably), webkit based*/
        scrollbar-width: none; /*FF 64+, Chrome ??+, webkit based, Edge ??+*/
        -ms-overflow-style: none; /*IE ??*/
        
        /*Style:*/
        width: 10em;
        height: 1em;
        line-height: 1em;
        padding: 5px;
        border: 1px solid #aaa;
        font-size: 20px;
        font-family: sans-serif;
}

.CETextInput::-webkit-scrollbar {
        display: none; /*Chrome ??, webkit based*/
}

/*OLD CODE:*/

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
        display: none;
}

.CETextInput * {
        display: inline;
        white-space: pre;
}
<!--NEW CODE:-->

<div class="CETextInput" contenteditable></div>

Cette solution a 3 problèmes avec rembourrages:

  1. Dans Firefox (testé le 2019-05-11, Firefox 66), il n'y a pas de remplissage correct lorsque le texte long est tapé. En effet, Firefox n'affiche pas le remplissage inférieur ou droit lors de l'utilisation du remplissage dans le même élément qui a une barre de défilement et lorsque le contenu est défilé jusqu'à la fin.
  2. Dans tous les navigateurs, il n'y a pas de remplissage lors du défilement d'un texte long en position médiane. Ça a l'air pire. <input type = "text"> n'a pas ce problème.
  3. Lorsque l'utilisateur appuie sur les navigateurs d'accueil ou de fin, faites défiler pour que les rembourrages ne soient pas visibles.

Pour résoudre ces problèmes, vous devez utiliser 3 éléments comme nous l'avons utilisé auparavant, mais dans ce cas, vous n'avez pas besoin d'utiliser la largeur de la barre de défilement. Notre solution à 3 éléments ne présente pas ces problèmes.


Autres problèmes (dans chaque solution):

  • Le flou lors du collage du texte se termine par un saut de ligne. Je vais penser comment y remédier.
  • Lors de l'utilisation de rembourrages, this.children [0] .focus () n'est pas suffisant dans les navigateurs Webkit (la position du curseur n'est pas celle où l'utilisateur a cliqué). Je vais penser comment y remédier.
  • Firefox (testé le 2019-05-11, Firefox 66): Lorsqu'un texte court est saisi, l'utilisateur ne peut pas sélectionner le dernier mot en double-cliquant à droite de celui-ci. J'y penserai.
  • Lorsque l'utilisateur commence la sélection de texte dans la page, il peut la terminer dans notre champ. Le <input type = "text"> habituel n'a pas ce comportement. Mais je ne pense pas que ce soit critique.
1
vitaliydev

Découvrez cette réponse que je viens de publier. Cela devrait vous aider:

Comment créer un contenu HTML5 d'une seule ligne Onglet éditable qui écoute Enter et Esc

Voici le balisage HTML:

<span contenteditable="false"></span>

Voici le jQuery/javascript:

$(document).ready(function() {
    $('[contenteditable]').dblclick(function() {
        $(this).attr('contenteditable', 'true');
        clearSelection();
        $(this).trigger('focus');
    });

    $('[contenteditable]').live('focus', function() {
        before = $(this).text();
        if($(this).attr('contenteditable') == "true") { $(this).css('border', '1px solid #ffd646'); }
    //}).live('paste', function() {
    }).live('blur', function() {
        $(this).attr('contenteditable', 'false');
        $(this).css('border', '1px solid #fafafa');
        $(this).text($(this).text().replace(/(\r\n|\n|\r)/gm,""));

        if (before != $(this).text()) { $(this).trigger('change'); }
    }).live('keyup', function(event) {
        // ESC=27, Enter=13
        if (event.which == 27) {
            $(this).text(before);
            $(this).trigger('blur');
        } else if (event.which == 13) {
            $(this).trigger('blur');
        }
    });

    $('[contenteditable]').live('change', function() {
        var $thisText = $(this).text();
        //Do something with the new value.
    });
});

function clearSelection() {
    if ( document.selection ) {
        document.selection.empty();
    } else if ( window.getSelection ) {
        window.getSelection().removeAllRanges();
    }
}

J'espère que cela aide quelqu'un !!!

1
trgraglia

Vous pouvez remplacer ce div par une entrée de texte (après l'appel de l'événement onclick).
J'ai utilisé quelque chose de similaire à ce plugin et cela a bien fonctionné.

0
shaggy