Issues using custom cursor with draggable element - javascript

Ok, so I have a draggable element that is contained by it's parent. I'm trying to add a custom cursor while dragging, including when the cursor moves off of the draggable element itself. And then when dragging stops, on mouseup, the cursor should return to default.
<div id="container">
<div class="draggable"></div>
</div>
$(document).ready(function() {
$(window).on("mouseup", function() {
$("*").removeClass("customcursor");
})
$(".draggable").on("mousedown", function() {
$("*").addClass("customcursor");
})
$(".draggable").draggable({
containment: "parent",
});
});
html {
background: lightblue;
}
body {
background-color: teal;
}
#container {
height: 400px;
width: 400px;
background-color: black;
}
.draggable {
height: 200px;
width: 200px;
background-color: red;
}
.customcursor {
cursor: url(http://media.fluke.com/images/8-icon-30x30.gif), default;
}
Here is a working fiddle.
However, this is giving me two issues.
First issue: While dragging, when the cursor moves onto the body element, the custom cursor is no longer in effect. However, when the cursor moves onto the html element, the custom cursor does work. Admittedly, this can be solved by adding a wrapper element, so the cursor is never on the body, but I'm still perplexed about why this is happening.
Second issue: After dragging for the first time, the cursor gets stuck with the custom cursor, even though the .customcursor class has been removed. This second issue can be solved if I add .customcursor class on the start and stop events of the draggable rather than on mousedown and mouseup, but I wanted the custom cursor on mousedown even if the draggable isn't moved.
If anyone has some insight into why these issues are occurring or good ways to fix them, I'd really appreciate the help.

Add !important to the the cursor change:
.customcursor {
cursor: url(http://media.fluke.com/images/8-icon-30x30.gif), default !important;
}
The body was being overrode by a direct element style:
element.style {
cursor: auto;
}
Also, for some reason, the cursor is being left behind as a direct style even after the class is removed. Remove it with JavaScript or jQuery:
$(window).on("mouseup", function() {
$("*").removeClass("customcursor");
//document.body.style.cursor = "";
$("body").css("cursor", "")
console.log("mouseup");
})
Fiddle: https://jsfiddle.net/ntyuuqq2/

Related

How to change cursor on multiple mouseovers

I am trying to change my cursor into different images depending on the object that it is hovering over inside my banner. Currently I only know to change the cursor style in CSS. But the cursor stays the same throughout. How do I replace the cursor image on mouseover in my javascript? I am only using jQuery and TweenMax as this is for an assignment.
Using CSS cursor property
Without using any pseudo-selectors in CSS, you can have a pretty good result by playing around with the cursor property. For example, you can select one cursor style from a range of available ones. Or even add your own by linking the URL of the icon.
For example, the code below will show a heart when you hover over the grey area:
.heart {
cursor: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/9632/heart.png"), auto;
width: 200px;
height: 200px;
background: grey;
}
<div class="heart"></div>
You can change the origin of the image's position relative to the actual mouse position by setting the x and y position along with the URL in the cursor property:
cursor: url(<URL>) [x y|auto];
Using JavaScript
Of course, you can handle this feature with JavaScript code. Here are several things we will need to achieve this:
creating an HTML element with the image of the cursor you want as background
using the onmouseenter, onmousemove and onmouseleave events
getting the position of the mouse on the page: properties pageX, pageY
setting the position of our cursor element to be at the position of the mouse (the actual mouse pointer will be hidden): with the transform CSS property.
There are several other tricks I have used to get it right: for example setting the boxes' overflow to be hidden so that the cursor elements can't be seen outside the box. Also, listening to the onmouseleave event allows us to hide the cursor element when the mouse is outside the box area.
I have made a little demo here, click Show code snippet > Run code snippet:
const showCursor = function(event) {
let cursor = event.target.querySelector('.cursor');
event.target.onmousemove = function(e) {
cursor.style.display = 'block'
let [x, y] = [e.pageX - e.target.offsetLeft - 20, e.pageY - e.target.offsetTop - 20]
cursor.style.transform = `translate(${x}px, ${y}px)`
}
event.target.onmouseleave = function(e) {
cursor.style.display = 'none'
}
}
.box {
cursor: none;
overflow: hidden;
width: 200px;
height: 200px;
background: pink;
display: inline-block;
margin: 10px;
}
.box:nth-child(1) {
background: aquamarine;
}
.box:nth-child(2) {
background: pink;
}
.box:nth-child(3) {
background: cornflowerblue;
}
.box:nth-child(4) {
background: lightcoral;
}
.cursor {
display: none;
width: 100px;
height: 100px;
}
#heart {
background: no-repeat url("https://png.icons8.com/color/50/000000/hearts.png");
}
#diamond {
background: no-repeat url("https://png.icons8.com/color/50/000000/diamonds.png")
}
#spade {
background: no-repeat url("https://png.icons8.com/metro/50/000000/spades.png")
}
#clubs {
background: no-repeat url("https://png.icons8.com/ios/50/000000/clubs-filled.png")
}
<div onmousemove="showCursor(event)" class="box">
<div id="diamond" class="cursor"></div>
</div>
<div onmouseenter="showCursor(event)" class="box">
<div id="heart" class="cursor"></div>
</div>
<div onmousemove="showCursor(event)" class="box">
<div id="spade" class="cursor"></div>
</div>
<div onmousemove="showCursor(event)" class="box">
<div id="clubs" class="cursor"></div>
</div>
The function showCursor() is called when the user's mouse enters one of the boxes with the attribute onmouseenter="showCursor(event)" (see HTML markup above).
Below I have provided the JavaScript code with comments explaining how it works:
const showCursor = function(event) {
// get the element object of the cursor of this box
let cursor = event.target.querySelector('.cursor');
// function that will be execute whenever the user moves inside the box
event.target.onmousemove = function(e) {
// the user is moving inside the box
// show the cursor element
cursor.style.display = 'block'
// calcultate the translate values of the cursor element
let [x, y] = [e.pageX - e.target.offsetLeft - 20, e.pageY - e.target.offsetTop - 20]
// apply these values to the style of the cursor element
cursor.style.transform = `translate(${x}px, ${y}px)`
}
// function that will be executed when the user leaves the box
event.target.onmouseleave = function(e) {
// the user's mouse left the box area
// hide the cursor element
cursor.style.display = 'none'
}
}
With a <svg> element
A while ago I answered a post on how to add an <svg> element as the cursor of the mouse. It's a little bit more advanced though. It's still a JavaScript solution but it involves using a <svg> element as the cursor instead of it being a simple <div> (as seen in the second point).

mouseover animation weird behaviour

I'm creating a simple animation with jquery.
The animation consists on a div being slided left when the mouse hovers it.
This is my html:
<div class="item">
<div class="content" onmouseover="over(this)" onmouseout="out(this)">
<div class="image">
<img src="http://aranciamato.it/wp-content/uploads/2013/09/insetti-700x250.jpg" width="300"/>
</div>
<div class="text">
Some useful content
</div>
</div>
</div>
This is the js:
function over(element) {
$(element).stop().animate({left: "-250"}, 500);
}
function out(element) {
$(element).stop().animate({left: "0"}, 500);
}
This is the css:
.item {
position: relative;
width: 300px;
height: 107px;
overflow:hidden;
}
.item .content {
position: absolute;
top:0;
left:0;
width: 600px;
height: 107px;
}
.item .content .image {
position:absolute;
top: 0;
left: 0;
}
.item .content .text{
position:absolute;
top: 0;
left: 300px;
width: 250px
}
.item {
border: 1px solid black;
}
The animation works properly, but I noticed that if you move the cursor over the left border of the .image div while the animation is running, it pauses for a moment and then resumes.
I don't know how to explain this better, so here you can find a JSFiddle, and here you can find a video that demonstrates the behaviour
My question is: Why is the animation behaving like this? How can I make the animation continue without pausing?
Use mouseenter and mouseleave instead
<div class="content" onmouseenter="over(this)" onmouseleave="out(this)">
DEMO
Your jsfiddle is working for me. Also, its better for you to use the $(el).hover() function jQuery provides, for example:
$('.content').hover(function() {
// Mouseover
}, function() {
// Mouseout
});
Well, you can use your hover event on the parent element (in this case .item) and animate the child element (.content). If you're using jQuery, just use .hover () instead of those two mouse events.
When you move the cursor out of the image div then a mouseout event fires - you move out of the image - and the out function is called, making the animation stop. Right after that the mouseover event fires - you move over the text - and the over function is called.
You attached the event handlers to the content div, i.e. they are called whenever the event fires on itself, but also when the event fires on one of it's descendants.
The reason why it's working with mousenter and mouseleave is that those events are handled differently by the browser. But be aware that they are not supported by all browsers.

Keep a second div visible if the mouse is over the first or second div

There are two divs; Div A (display:none by default) and Div B (visible all the time). How would one make it so if mouse moves over Div B, Div A becomes visible. Div A should remain visible if the mouse cursor is on either Div A or Div B, otherwise Div A should be hidden.
I'm using jQuery plugin hoverIntent for this.
$(".the-dropdown").hoverIntent( function(){
$(".the-dropdown").show();
}, function(){
$(".the-dropdown").hide();
});
$(".menu-item").hoverIntent( function(){
$(".the-dropdown").show();
}, function(){
$(".the-dropdown").hide();
});
jsfiddle
Hmm, try something like this.
HTML:
<div id="a"></div>
<div id="b"></div>
CSS:
div {
height: 200px;
width: 200px;
}
#a {
background: #0f0;
display: none;
}
#b {
background: #f0f;
}
JS:
$('#a, #b').hover(function() {
$('#a').show();
}, function() {
$('#a').hide();
});
Fiddle
Or in your specific case:
$(".the-dropdown, .menu-item").hover( function(){
$(".the-dropdown").show();
}, function(){
$(".the-dropdown").hide();
});
hoverIntent is a plug-in that attempts to determine the user's
intent... like a crystal ball, only with mouse movement! It is similar
to jQuery's hover method. However, instead of calling the handlerIn
function immediately, hoverIntent waits until the user's mouse slows
down enough before making the call.
Why? To delay or prevent the accidental firing of animations or ajax
calls. Simple timeouts work for small areas, but if your target area
is large it may execute regardless of intent. That's where hoverIntent
comes in...
If you would like to use the hoverIntent plugin you can download it here:
http://cherne.net/brian/resources/jquery.hoverIntent.html
Working Example Using hoverIntent
$(".menu-item").hoverIntent({
over: function () {
$(".the-dropdown").slideDown();
},
out: function () {
$(".the-dropdown").slideUp();
},
timeout: 500,
interval: 500
});
<div class="menu-item">Hover this for half a second
<div class="the-dropdown"></div>
</div>
div {
height: 200px;
width: 200px;
}
.the-dropdown {
background: red;
display: none;
position:relative;
top:182px;
}
.menu-item {
background: blue;
}

Loss of CSS class after style update

I am applying a class (rShift) to a DIV that acts as a menu tab. The class gives it a :hover behaviour. On clicking the DIV, I bring in a menu on to the screen. On collapsing the menu, I loose the class and the :hover behaviour too.
I am using jQuery UI and have even tried .addClass('') to apply the lost class, but it did not work.
See it at: http://pastebin.com/hdb8Y2Ke | http://bharath.lohray.com/ftree/
When the page is initially loaded, you can see a tab at the top left corner of the page, just under the search box. On hovering the mouse it jumps out a few pixels. On clicking, the menu appears. On clicking the tab again, the menu collapses and the jump out effect is lost :-(.
What am I doing wrong?
The class is being added, but on click, you are applying an inline style to the leftmenutab directly via jquery. This style (left) overrides any styles you have in your style sheets.
I would remove the inline styles you are applying via jquery and add the styles you want to your css.
Create styles like this:
.leftMenuTab[data-state="expanded"] { left: 100px; }
.leftMenuTab[data-state="collapsed"] { left: 0; }
and remove these lines from your javascript:
$(".leftMenuTab").css("left", "+=100px");
$(".leftMenuTab").css("left", "-=100px");
Alternatively, add and remove classes from your leftMenuTab and leftMenu on click and style them through CSS. Something like this:
HTML:
<div class="leftMenu">Hello Menu</div>
<div class="leftMenuTab" data-state="collapsed">
<span class="charIcon"></span>
</div>
JS:
$('.leftMenuTab').click(function(e) {
$('.leftMenuTab,.leftMenu').toggleClass('expanded');
}
CSS:
.leftMenuTab .charIcon:after{
content:'>>';
}
.leftMenuTab.expanded .charIcon:after{
content:'<<';
}
.leftMenuTab {
background-color: #FFFFFF;
border-color: #000000;
cursor: pointer;
float: left;
padding-right: 5px;
position: absolute;
text-align: right;
top: 45px;
width: 30px;
z-index: 2;
left: -10px;
}
.leftMenuTab:hover {left:0;}
.leftMenuTab.expanded { left:100px;}
After you modify the style of the element, the style left: 0px; is left in the DIV, this neglated the effect of the hover.
This is the relevant code:
$('.leftMenuTab').click(function(e) {
temp = $(this);
if ($('.leftMenuTab').attr('data-state') == "collapsed") {
$(".charIcon", this).html("«");
$('.leftMenuTab').attr('data-state', 'expanded');
$(".leftMenu").css("left", "+=110px");
$(".leftMenuTab").css("left", "+=100px");
} else {
$(".charIcon", this).html("»");
$('.leftMenuTab').attr('data-state', 'collapsed');
$(".leftMenu").css("left", "-=110px");
$(".leftMenuTab").css("left", "-=100px");
$(this).addClass("rShift");
}
});
The quickest fix is to erase the left style instead of modyfing it (and you don't need to add the class again):
$('.leftMenuTab').click(function(e) {
temp = $(this);
if ($('.leftMenuTab').attr('data-state') == "collapsed") {
$(".charIcon", this).html("«");
$('.leftMenuTab').attr('data-state', 'expanded');
$(".leftMenu").css("left", "+=110px");
$(".leftMenuTab").css("left", "+=100px");
} else {
$(".charIcon", this).html("»");
$('.leftMenuTab').attr('data-state', 'collapsed');
$(".leftMenu").css("left", ""); //set to empty string
$(".leftMenuTab").css("left", ""); //set to empty string
//$(this).addClass("rShift"); //Not needed
}
});
Note: this was tested with a local copy from your web.

contenteditable single-line input

For an application we're developing at the company where I work, we need an input that supports inserting emoticons inside our JS-based web app. We're currently using an input with the emoticon shortcodes (ie ':-)') and would like to switch to inserting actual, graphical images.
Our original plan was to use a contenteditable <div>. We're using listeners for the paste event as well as the different key/mouse interactions to ensure no unwanted markup enters the contenteditable (we strip text out of its container tags and leave only image tags that we inserted ourselves).
However, the problem right now is that the div resizes if you put in enough content (ie its height increases). We don't want this to happen, nor is it acceptable for the text to just be hidden (ie plain overflow: hidden). So:
Is there a way to make the contenteditable div behave like a single-line input?
I'd like it best if there is a relatively simple attribute/css property that I've missed that will do what I want, but if necessary CSS+JS suggestions will also be appreciated.
[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>​
Other answers are wrong and contain few mistakes (on 2019-05-07). Other solutions suggest to use "white-space: nowrap" (prevents carrying to another line) + "overflow: hidden" (prevents long text going beyond the field) + hiding <br> and other.
First mistake in that solutions is "overflow: hidden" also prevents scrolling the text. User will not be able to scroll the text by:
Pressing mouse middle button
Selecting the text and moving mouse pointer to the left or right
Using horizontal mouse scroll (when user have such a thing)
The only way he can scroll is using keyboard arrows.
You can solve this problem by using "overflow: hidden" and "overflow: auto" (or "scroll") at the same time. You should create parent div with "overflow: hidden" to hide content user should not see. This element must have input borders and other design. And you should create child div with "overflow-x: auto" and "contenteditable" attribute. This element will have scrollbar so user can scroll it without any limitations and he will not see this scrollbar because of hiding overflow in parent element.
Example of 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>
Step 2: Solving problem with <br> and other:
Also there is a problem that user or extensions can paste
<br> (can be pasted by user)
<img> (may have big size) (can be pasted by user)
elements with another "white-space" value
<div> and other elements that carry text to another line
elements with unsuitable "display" value
But advise to hide all <br> is wrong too. That is because Mozilla Firefox adds <br> element to empty field (I guess it may be workaround of bug with text cursor disappearing after deleting last character; checked in Firefox 66 released on 2019-03-19). If you hide this element then when user moves focus to field caret will be set in this hidden <br> element and text cursor will be hidden too (always).
You can fix this if you will be <br> when you know field is empty. You need some javascript here (you cannot use :empty selector because field contains <br> elements and not empty). Example of 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>
Step 3: Solving problem with getting value:
We hided <br> elements so "innerText" value will not contain them. But:
When "empty" class is set result may contain <br> elements.
Your other styles or extensions may override "display: none" by "!important" mark or by rule with higher priority.
So when you get value you should make replace to avoid accidental getting line breaks:
s = s.replace(/[\r\n]+/g, '');
Do not use javascript for hiding <br>
Also you could solve the problem with <br> by removing them by javascript but this is very bad solution because after every removing user cannot use "undo" action anymore for canceling changes was made before removing.
Also you could use document.execCommand('delete') to delete <br> but it is hard to implement + user can undo your deletion and restore <br> elements.
Adding placeholder
It was not asked in question but I guess many people using single-line contenteditable elements will need it. Here is example how to make placeholder using css and "empty" class we talked above:
//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 with only one div and "scrollbar-width"
You can also use only one div by setting "overflow-x: auto", "overflow-y: hidden" and "scrollbar-width: none". But "scrollbar-width" is new property and works only in Firefox 64+ and no other browsers yet.
You can also add:
webkit-prefixed version: "-webkit-scrollbar-width: none"
non-standardized ".CETextInput::-webkit-scrollbar { display: none; }" (for webkit-based browsers)
"-ms-overflow-style: none"
I would not recommend to use this solution, but here is example:
//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>
This solution has 3 problems with paddings:
In Firefox (tested on 2019-05-11, Firefox 66) there is no right padding when long text is typed. That is because Firefox does not display bottom or right padding when using padding in the same element that have scrollbar and when content is scrolled to the end.
In all browsers there is no padding when scrolling long text in middle position. It looks worse. <input type="text"> does not have this problem.
When user press home or end browsers scroll to place paddings are not visible.
To solve these problems you need use 3 elements like we used before but in this case you don't need use scrollbar-width. Our solution with 3 elements does not have these problems.
Other problems (in every solution):
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.
I think you are looking for a contenteditable div with only one line of text that scrolls horizontally when it overflows the div. This should do the trick: 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>
The min-height: 40px incorporates the height for when the horizontal scroll bar appears. A min-height:20px would automatically expand when the horizontal scroll bar appears, but this doesn't work in IE7 (though you could use conditional comments to apply separate styling if you wanted it).
I adapted the #tw16 accepted solution (on 5th Dec 2019) to add in scrolling. The trick was to add scrolling using overflow-x: auto but then hide the scrollbar (https://stackoverflow.com/a/49278385)
/* Existing Solution */
[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;
}
/* Make Scrollable */
[contenteditable="true"].single-line {
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* Internet Explorer 10+ */
}
[contenteditable="true"].single-line::-webkit-scrollbar { /* WebKit */
width: 0;
height: 0;
}
<div contenteditable="true" class="single-line">
This should scroll when you have really long text!
</div>​
Here's a relatively simple solution that uses the contenteditable's input event to scan the dom and filter out various flavors of new lines (so it should be robust against copy/paste, drag 'n drop, hitting enter on the keyboard, etc). It condenses multiple TextNodes into single TextNodes, strips newlines from TextNodes, kills BRs, and puts a "display: inline" on any other element that it touches. Tested on Chrome, no guarantees anywhere else.
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>
Or here's the fiddle: http://jsfiddle.net/tG9Qa/
If you want a different way of solving it other than changing the requirements, with a little display:table it is fully 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>
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;
}
})

Categories