programing tip

contenteditable 한 줄 입력

itbloger 2021. 1. 11. 07:44
반응형

contenteditable 한 줄 입력


제가 일하는 회사에서 개발중인 애플리케이션의 경우 JS 기반 웹 앱에 이모티콘 삽입을 지원하는 입력이 필요합니다. 현재 이모티콘 단축 코드 (예 : ':-)')가있는 입력을 사용하고 있으며 실제 그래픽 이미지 삽입으로 전환하려고합니다.

원래 계획은 contenteditable <div>. 우리는 원하지 않는 마크 업이 contenteditable에 들어 가지 않도록하기 위해 붙여 넣기 이벤트와 다른 키 / 마우스 상호 작용에 리스너를 사용하고 있습니다 (컨테이너 태그에서 텍스트를 제거하고 직접 삽입 한 이미지 태그 만 남김).

그러나 현재 문제는 충분한 콘텐츠를 넣으면 div의 크기가 조정된다는 것입니다 (즉, 높이가 증가 함). 우리는 이런 일이 일어나기를 원하지 않으며 텍스트가 숨겨지는 것을 허용하지도 않습니다 (예 : 일반 overflow: hidden). 그래서:

contenteditable div가 한 줄 입력처럼 동작하도록 만드는 방법이 있습니까?

내가 놓친 비교적 간단한 속성 / css 속성이 내가 원하는 것을 할 수 있다면 가장 좋겠지 만 필요한 경우 CSS + JS 제안도 감사 할 것입니다.


[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>​


나는 contenteditable div그것이 오버플로 될 때 가로로 스크롤되는 한 줄의 텍스트 만 찾고 있다고 생각 합니다 div. 이것은 트릭을해야합니다 : 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>

min-height: 40px경우 수평 스크롤 막대가 나타납니다의 높이를 포함한다. min-height:20px가로 스크롤 막대가 나타나면 A 가 자동으로 확장되지만 IE7에서는 작동하지 않습니다 (원하는 경우 조건부 주석을 사용하여 별도의 스타일을 적용 할 수 있음).


다음은 contenteditable의 입력 이벤트를 사용하여 dom을 스캔하고 다양한 종류의 새 줄을 필터링하는 비교적 간단한 솔루션입니다 (따라서 복사 / 붙여 넣기, 끌어서 놓기, 키보드에서 Enter 누르기 등에 대해 견고해야합니다). 여러 TextNode를 단일 TextNode로 압축하고, TextNode에서 줄 바꿈을 제거하고, BR을 죽이고, 닿는 다른 요소에 "display : inline"을 넣습니다. Chrome에서 테스트되었으며 다른 곳에서는 보장되지 않습니다.

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>

또는 여기 바이올린이 있습니다 : http://jsfiddle.net/tG9Qa/


요구 사항을 변경하는 것 외에 다른 해결 방법을 원한다면 조금만 display:table있으면 완전히 가능합니다 =)

.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>


다른 답변은 잘못되었으며 실수가 거의 없습니다 (2019-05-07). 다른 솔루션은 "white-space : nowrap"(다른 줄로 이동 방지) + "overflow : hidden"(필드를 넘어가는 긴 텍스트 방지) + 숨기기 <br> 및 기타를 사용하는 것이 좋습니다.

솔루션의 첫 번째 실수는 "오버플로 : 숨김"으로도 텍스트 스크롤을 방지합니다. 사용자는 다음을 기준으로 텍스트를 스크롤 할 수 없습니다.

  • 마우스 가운데 버튼 누르기
  • 텍스트를 선택하고 마우스 포인터를 왼쪽 또는 오른쪽으로 이동
  • 수평 마우스 스크롤 사용 (사용자가 그런 일이있을 때)

그가 스크롤 할 수있는 유일한 방법은 키보드 화살표를 사용하는 것입니다.

"overflow : hidden"과 "overflow : auto"(또는 "scroll")를 동시에 사용하여이 문제를 해결할 수 있습니다. 사용자가 볼 수없는 콘텐츠를 숨기려면 "overflow : hidden"으로 상위 div를 만들어야합니다. 이 요소에는 입력 테두리 및 기타 디자인이 있어야합니다. 그리고 "overflow-x : auto"및 "contenteditable"속성을 사용하여 자식 div를 만들어야합니다. 이 요소에는 스크롤바가 있으므로 사용자는 제한없이 스크롤 할 수 있으며 부모 요소에 오버플로를 숨겨서이 스크롤바를 볼 수 없습니다.

솔루션의 예 :

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>


2 단계 : <br> 및 기타 문제 해결 :

또한 사용자 또는 확장 프로그램이 붙여 넣을 수있는 문제가 있습니다.

  • <br> (사용자가 붙여 넣을 수 있음)
  • <img> (크기가 클 수 있음) (사용자가 붙여 넣을 수 있음)
  • 다른 "공백"값이있는 요소
  • <div> 및 텍스트를 다른 줄로 전달하는 기타 요소
  • 부적절한 "표시"값을 가진 요소

그러나 모든 <br> 을 숨기라고 조언하는 것도 잘못되었습니다. 이는 Mozilla Firefox가 빈 필드에 <br> 요소를 추가하기 때문입니다 (마지막 문자를 삭제 한 후 텍스트 커서가 사라지는 버그의 해결 방법 일 수 있습니다. 2019-03-19에 릴리스 된 Firefox 66에서 확인 됨). 이 요소를 숨기면 사용자가 필드 캐럿으로 포커스를 이동할 때이 숨겨진 <br> 요소에 설정되고 텍스트 커서도 (항상) 숨겨집니다.

필드가 비어 있다는 것을 알고있을 때 <br> 문제를 해결할 수 있습니다. 여기에 자바 스크립트가 필요합니다 (필드가 비어 있지 않은 <br> 요소를 포함하기 때문에 : empty 선택기를 사용할 수 없습니다). 솔루션의 예 :

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>


3 단계 : 가치를 얻기위한 문제 해결 :

<br> 요소를 숨겼으므로 "innerText"값에 포함되지 않습니다. 그러나:

  1. "빈"클래스가 설정되면 결과에 <br> 요소가 포함될 수 있습니다.
  2. 다른 스타일 또는 확장 프로그램은 "! important"표시 또는 더 높은 우선 순위의 규칙에 의해 "display : none"을 재정의 할 수 있습니다.

따라서 가치를 얻을 때 실수로 줄 바꿈이 발생하지 않도록 교체해야합니다.

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


숨기기 위해 자바 스크립트를 사용하지 마세요. <br>

또한 자바 스크립트로 제거하여 <br>의 문제를 해결할 수 있지만 모든 제거 사용자가 더 이상 "취소"작업을 사용할 수 없기 때문에 제거하기 전에 변경 사항을 취소 할 수 있기 때문에 이것은 매우 나쁜 해결책입니다.

또한 document.execCommand ( 'delete')를 사용하여 <br>을 삭제할 수 있지만 구현하기가 어렵고 사용자가 삭제를 취소하고 <br> 요소를 복원 할 수 있습니다.


자리 표시 자 추가

질문은 없지만 한 줄 콘텐츠 편집 요소를 사용하는 많은 사람들이 필요하다고 생각합니다. 다음은 위에서 언급 한 CSS 및 "빈"클래스를 사용하여 자리 표시자를 만드는 방법의 예입니다.

//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.-->


하나의 div와 "스크롤바 너비"만있는 솔루션

"overflow-x : auto", "overflow-y : hidden"및 "scrollbar-width : none"을 설정하여 하나의 div 만 사용할 수도 있습니다. 그러나 "scrollbar-width"는 새로운 속성이며 Firefox 64 이상에서만 작동하며 다른 브라우저에서는 아직 작동하지 않습니다.

다음을 추가 할 수도 있습니다.

  • 웹킷 접두사 버전 : "-webkit-scrollbar-width : none"
  • 표준화되지 않은 ".CETextInput ::-webkit-scrollbar {display : none;}"(웹킷 기반 브라우저 용)
  • "-ms-overflow-style : 없음"

이 솔루션을 사용하지 않는 것이 좋지만 다음은 예입니다.

//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>

이 솔루션에는 패딩에 세 가지 문제 가 있습니다.

  1. Firefox (2019-05-11, Firefox 66에서 테스트 됨)에서는 긴 텍스트를 입력 할 때 오른쪽 패딩이 없습니다. 이는 Firefox가 스크롤바가있는 동일한 요소에 패딩을 사용하고 콘텐츠가 끝까지 스크롤 될 때 하단 또는 오른쪽 패딩을 표시하지 않기 때문입니다.
  2. 모든 브라우저에서 중간 위치에서 긴 텍스트를 스크롤 할 때 패딩이 없습니다. 더 나빠 보입니다. <input type = "text">에는이 문제가 없습니다.
  3. 사용자가 홈 또는 끝 브라우저를 눌러 패딩을 배치하기 위해 스크롤하면 보이지 않습니다.

이러한 문제를 해결하려면 이전에 사용한 것과 같은 3 가지 요소를 사용해야하지만이 경우 스크롤바 너비를 사용할 필요가 없습니다. 3 가지 요소로 구성된 솔루션에는 이러한 문제가 없습니다.


기타 문제 (모든 솔루션에서) :

  • Blur on pasting text ends with line break. I will think how to fix it.
  • When using paddings this.children[0].focus() is not enough in webkit-based browsers (cursor position is not where user clicked). I will think how to fix it.
  • Firefox (tested on 2019-05-11, Firefox 66): When short text is typed user cannot select last word by double clicking on the right of it. I will think about it.
  • When user starts text selection in the page he can end it in our field. Usual <input type="text"> does not have this behavior. But I don't think it is critical.

So, for posterity: the simplest solution is to get your product manager to change the requirements so you can do multiline editing. This is what ended up happening in our case.

However, before that happened, I ended up going quite a way in creating a manually moving single-line richtext editor. I wrapped it up in a jQuery plugin in the end. I don't have time to finish it up (there are probably bugs in IE, Firefox works best and Chrome works pretty well - comments are sparse and sometimes not very clear). It uses parts of the Rangy library (extracted because I didn't want to rely on the complete library) to get screen positions of selections in order to test for mouse position vs. selection (so you can drag selections and move the box).

Roughly, it works by using 3 elements. An outer div (the thing you call the plugin on), which gets overflow: hidden, and then 2 levels of nesting inside it. The first level is absolutely positioned, the second level is the actual contenteditable. This separation is necessary because otherwise some browsers will give the contenteditable absolutely positioned element grippies, to let the user move it around...

In any case, then there is a whole bunch of code to move the absolutely positioned element around inside the top element, thus moving the actual contenteditable. The contenteditable itself has white-space nowrap, etc. etc. to force it to stay a single line.

There is also code in the plugin that strips out anything that isn't an image (like br, tables, etc. etc.) from any text that's pasted / dragged into the box. You need some parts of this (like the brs, stripping/normalizing paragraphs, etc.) but maybe you would normally want to keep links, em, strong and/or some other formatting.

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


Check out this answer I just posted. This should help you out:

How to create a HTML5 single line contentEditable tab which listens to Enter and Esc

Here is the HTML markup:

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

Here is the 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();
    }
}

Hope this helps someone!!!


You can replace this div with text input (after onclick event is called).
I have used something similar to this plugin and it worked fine.


with jQuery I have set a .keypress event and then tests for e.keyCode == 13 (return key) and if is return false from the event and the editing is not able to make multilines

$('*[contenteditable=yes]').keypress(function(e) {
  if(e.keyCode == 13 && !$(this).data('multiline')) {
    return false;
  }
})

ReferenceURL : https://stackoverflow.com/questions/6831482/contenteditable-single-line-input

반응형