I'm using html2canvas to render html contents to image. But it supports only single blank space between word and also all text displayed only in One.
Example 1
if text is `Word1 Word2` it become to `word1 word2`
Example 2
This is First line
This is Second Line
Image:
THis is First line This is Second Line
I looked in to the html2canvas Code and I believe below these two functions are responsible for drawing the text and spaces. Help me how can i achieve my target.
function renderText(el, textNode, stack) {
var ctx = stack.ctx,
color = getCSS(el, "color"),
textDecoration = getCSS(el, "textDecoration"),
textAlign = getCSS(el, "textAlign"),
metrics,
textList,
state = {
node: textNode,
textOffset: 0
};
if (Util.trimText(textNode.nodeValue).length > 0) {
textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ?
textNode.nodeValue.split(/(\b| )/)
: textNode.nodeValue.split("");
metrics = setTextVariables(ctx, el, textDecoration, color);
if (options.chinese) {
textList.forEach(function(word, index) {
if (/.*[\u4E00-\u9FA5].*$/.test(word)) {
word = word.split("");
word.unshift(index, 1);
textList.splice.apply(textList, word);
}
});
}
textList.forEach(function(text, index) {
var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
if (bounds) {
drawText(text, bounds.left, bounds.bottom, ctx);
renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
}
});
}
}
function drawText(currentText, x, y, ctx){
if (currentText !== null && Util.trimText(currentText).length > 0) {
ctx.fillText(currentText, x, y);
numDraws+=1;
}
}
html2canvas render the textarea or Input box value in the one line and trim all more than one spaces between words. So I found solution by converting text area into the div tag , Check out html contenteditable Attribute
Replace <textarea></textarea> with <div contenteditable="true"></div>
if you want to have the same textarea behavior to the div with jquery then use this code
$( '#EDITABLE' ).focus();
var selection = window.getSelection();
var range = document.createRange();
var div = $('#div2').get(0);
range.setStartBefore(div);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
// cursor should now be between div1 and div2
range = window.getSelection().getRangeAt(0);
console.log("range object returned is: ", range);
See Example: http://jsfiddle.net/9ZZpX/3/
I know it's old but, here is the workaround I came with using jquery/JS for your "Example 2":
var oldTextArea = $('#textArea').replaceWith("<div id='divForTA' class='divTextArea'>" + $('#textArea').val().replace(/\n/g, "<br>") + "</div>");
var el = document.querySelector("#container");
html2canvas(el).then(canvas => {
canvas.toDataURL();
$('#divForTA').replaceWith(oldTextArea);
});
So basicaly you replace your textArea with a "div" and once the canvas is rendered as image you revert the new created "div" to "textArea"
You can style the "div" using the "id" to add a border to make it looks like "textarea" like this:
#divForTA {
border: solid 1px lightgrey;
}
I suggest to put a comment in the github html2canvas issue so at some point this will be fixed
https://github.com/niklasvh/html2canvas/issues/2008
Hope this will help someone :-)
I tried all kinds of things, including a parameter that exists in a specific version of html2canvas which is: letterRendering: true in the options of your object.
In my case, I receive an error that this option doesn't exists in the lib that I've download
html2canvas(document.querySelector("#capture2image"), {
allowTaint: true,
useCORS: true,
logging: false
})
So i just did a simple/non-maintainable thing:
{{ name.replace(" ", " ") }}
maybe it will help others who also tried everything yet nothing worked ....
there is a race condition in html2canvas.
try this:
setTimeout(() => {
canvas = html2canvas(element, {scale:1});
}, 0)
Related
I have a spell check solution that uses a content editable div and inserts span tags around words that are misspelled. Every time the inner html of the div is updated, the cursor moves to the beginning of the div.
I know I can move the cursor to the end of the div if the user adds new words to the end of the sentence (code below).
Old Text: This is a spell checker|
New Text: This is a spell checker soluuution|
var range = document.createRange();
range.selectNodeContents(element[0]);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
However, I am unable to retain the cursor position if the user adds words in the middle of a sentence.
Old Text: This is a spell checker
New Text: This is a new spell checker|
In the above case, the cursor goes to the end of the div when it should be after "new".
How do I retain the cursor position? Since I am updating the html and adding nodes, saving the range before the update and adding it to the selection object isn't working.
Thanks in advance.
As far as I know, changing the content of the div will always have problem.
So here is the solution that I came with. Please type error word such as helloo, dudeee
This should ideally work for textarea as well.
Solution details:
Use a ghost div with same text content
Use transparent color for the ghost div
Use border-bottom for the ghost div span text
Change zIndex so that it does't appear infront
// some mock logic to identify spelling error
const errorWords = ["helloo", "dudeee"];
// Find words from string like ' Helloo world .. '
// Perhaps you could find a better library you that does this logic.
const getWords = (data) =>{
console.log("Input: ", data);
const allWords = data.split(/\b/);
console.log("Output: ", allWords)
return allWords;
}
// Simple mock logic to identify errors. Now works only for
// two words [ 'helloo', 'dudeee']
const containsSpellingError = word => {
const found = errorWords.indexOf(word) !== -1;
console.log("spell check:", word, found);
return found;
}
const processSpellCheck = text => {
const allWords = getWords(text);
console.log("Words in the string: ", allWords);
const newContent = allWords.map((word, index) => {
var text = word;
if(containsSpellingError(word.toLowerCase())) {
console.log("Error word found", word);
text = $("<span />")
.addClass("spell-error")
.text(word);
}
return text;
});
return newContent;
}
function initalizeSpellcheck(editorRef) {
var editorSize = editorRef.getBoundingClientRect();
var spellcheckContainer = $("<div />", {})
.addClass("spell-check")
.prop("spellcheck", "false");
var spellcheckSpan = $("<span />")
.addClass("spell-check-text-content")
.css({
width: editorSize.width,
height: editorSize.height,
position: "absolute",
zIndex: -1
});
var text = $(editorRef).text();
var newContent = processSpellCheck(text);
spellcheckSpan.append(newContent);
spellcheckContainer.append(spellcheckSpan);
spellcheckContainer.insertBefore(editorRef);
$(editorRef).on("input.spellcheck", function(event) {
var newText = $(event.target).text();
var newContent = processSpellCheck(newText);
$(".spell-check .spell-check-text-content").text("");
$(".spell-check .spell-check-text-content").append(newContent);
});
}
$(document).ready(function() {
var editor = document.querySelector("#editor");
initalizeSpellcheck(editor);
});
#editor {
border: 1px solid black;
height: 200px;
}
.spell-check {
color: transparent;
}
.spell-error {
border-bottom: 3px solid orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="editor" contenteditable="true" spellcheck="false">
dudeee
</div>
This answer might work from SitePoint:
Store the selection x, y:
cursorPos=document.selection.createRange().duplicate();
clickx = cursorPos.getBoundingClientRect().left;
clicky = cursorPos.getBoundingClientRect().top;
Restore the selection:
cursorPos = document.body.createTextRange();
cursorPos.moveToPoint(clickx, clicky);
cursorPos.select();
SitePoint Article: Saving/restoring caret position in a contentEditable div
Update 25.10.2019:
The solution mentioned above doesn't work anymore since functions are used that are deprecated. Does chrome supports document.selection?
I'm all new to this, but after spending a week trying to find an answer, I thought I would try asking directly.
I am building a text editor using javascript and jquery. I have a textarea (with contenteditable), a stylesheet and a js script. What I want is that for each letter pressed, the kerning will be random. I achieved that with a simple function, but I don't want ALL textarea text to have this kerning, only the last letter pressed and so on and so on, so this type of thing would be the result:
simulation
There is what I have so far in my js file:
$(document).ready(
function() {
$('#textarea').keypress(function(){
var KerningRandom = Math.floor((Math.random()*90)-20);
$(this).css('letter-spacing',KerningRandom);
});
Here is my jsfiddle that actually doesn't work in jsfiddle and I don't get why as it works fine in local...?
Thanks!
You cannot address individual characters ( and so glyphs ) in CSS. Only ::first-letter.
Options you have:
convert all characters to individual spans. That's too much I think.
use <canvas> to render text and so to implement text flow layout from scratch.
You can find a working plunker of what you want to achieve there (I forked yours).
https://jsfiddle.net/1gesLgsa/2/
Full code :
//Code from https://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
//Namespace management idea from http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
(function( cursorManager ) {
//From: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
var voidNodeTags = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];
//From: https://stackoverflow.com/questions/237104/array-containsobj-in-javascript
Array.prototype.contains = function(obj) {
var i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
}
//Basic idea from: https://stackoverflow.com/questions/19790442/test-if-an-element-can-contain-text
function canContainText(node) {
if(node.nodeType == 1) { //is an element node
return !voidNodeTags.contains(node.nodeName);
} else { //is not an element node
return false;
}
};
function getLastChildElement(el){
var lc = el.lastChild;
while(lc && lc.nodeType != 1) {
if(lc.previousSibling)
lc = lc.previousSibling;
else
break;
}
return lc;
}
//Based on Nico Burns's answer
cursorManager.setEndOfContenteditable = function(contentEditableElement)
{
while(getLastChildElement(contentEditableElement) &&
canContainText(getLastChildElement(contentEditableElement))) {
contentEditableElement = getLastChildElement(contentEditableElement);
}
var range,selection;
if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
}
else if(document.selection)//IE 8 and lower
{
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
range.select();//Select the range (make it the visible selection
}
}
}( window.cursorManager = window.cursorManager || {}));
// ACTUAL CODE MADE FOR THIS ANSWER
$('#textarea').keypress(function(event) {
event.preventDefault();
var KerningRandom = Math.floor((Math.random() * 90));
if ($("#last").length > 0)
{
var previousLast = $("#textarea #last").html();
$("#textarea #last").remove();
}
else
var previousLast = "";
$("#textarea").html($("#textarea").html().slice() + previousLast + "<span id='last'>" + String.fromCharCode(event.which) + "</span>")
$("#last").css('margin-left', KerningRandom + "px");
var editableDiv = document.getElementById("textarea");
cursorManager.setEndOfContenteditable(editableDiv)
});
var editableDiv = document.getElementById("textarea");
cursorManager.setEndOfContenteditable(editableDiv)
Point by point explanation :
$('#textarea').keypress(function(event) {
event.preventDefault();
var KerningRandom = Math.floor((Math.random() * 90));
if ($("#last").length > 0)
{
var previousLast = $("#textarea #last").html();
$("#textarea #last").remove();
}
else
var previousLast = "";
$("#textarea").html($("#textarea").html() + previousLast + "<span id='last'>" + String.fromCharCode(event.which) + "</span>")
$("#last").css('margin-left', KerningRandom + "px");
var editableDiv = document.getElementById("textarea");
cursorManager.setEndOfContenteditable(editableDiv)
});
The event.preventDefault() prevent the letter to be added when pressing a key.
Then, we calculate our left margin value, save the previous last letter we had and remove the span that contains the last letter as it's not the last letter anymore.
We append the previous last letter , and the span that has a random left margin (to simulate the kerning) and the value of the pressed key (thanks to
How to find out what character key is pressed?) to the actual content.
After that, we needed to move the carret at the end of the textarea manually, because it would stay at the beginning otherwise.
For that, I used the code from
How to move cursor to end of contenteditable entity so goes there for explanation.
I have seen a few questions about this on StackOverflow, but it seems hard to find a jQuery-based solution. Hence, I would like to ask this question.
I would like to replace text on-the-fly inside a div with the attribute contenteditable="true".
I'm looking for a jQuery-based solution that will do the following:
Automatically replace written text on-the-fly (as you type)
Being able to continue writing (whilst the replacement is being done)
I looked at SC Editor (http://www.sceditor.com/), it seems like it does exactly that (for instance, if you try type :) it gets replaced by an emoticon.
I think a good start would be an array with all the elements to replace:
$.settings = {
path: 'https://example.com/images/',
emoticons: {
':(' : 'stupid.jpg',
':)' : 'smart.jpg',
}
}
I have been unable to find good examples of this. Would be happy if somebody can share their thoughts and any code regarding this.
How would the replacement get done in the best possible way, with the above code?
I found this. If you adjust it, it might suit your needs. It replaces { with {} and ( with () and the cursor ends up in the middle.
<script type="text/javascript">
$(document).ready(function () {
$("#d").keypress(function (e) {
var charTyped = String.fromCharCode(e.which);
if (charTyped == "{" || charTyped == "(") {
// Handle this case ourselves
e.preventDefault();
var sel = window.getSelection();
if (sel.rangeCount > 0) {
// First, delete the existing selection
var range = sel.getRangeAt(0);
range.deleteContents();
// Insert a text node with the braces/parens
var text = (charTyped == "{") ? "{}" : "()";
var textNode = document.createTextNode(text);
range.insertNode(textNode);
// Move the selection to the middle of the text node
range.setStart(textNode, 1);
range.setEnd(textNode, 1);
sel.removeAllRanges();
sel.addRange(range);
}
}
});
});
</script>
</head>
<body>
<div id="d" contentEditable="true">....</div>
</body>
</html>
$('div').keyup(function(){
//make here for loop which replace all emoticons
$(this).text().replace(':(', 'stupid.jpg');
});
Posting what I eventually wrote after failing to find an answer to this question. I hope that it will be helpful to someone else who comes to this question looking for an answer (:
I'm going to post a much more general find and replace solution (contained in a class). This is for content editable divs and works while the user is typing, additionally it does not affect the caret position. This implementation uses a case insensitive search (although it would be trivial to disable this in the code). Another advantage this has is that it will work even if you are typing in the middle of a paragraph (not just at the end of the line) and will work on pasted text. Give it a go!
class FindAndReplace {
constructor($contentEditable, findAndReplaceData) {
var self = this;
$contentEditable.on('input blur', function () {
var textNodes = self.getTextNodes($contentEditable);
textNodes.each(function (i) {
// Perform all replacements on text
findAndReplaceData.forEach(function (findAndReplaceDatum) {
var find = findAndReplaceDatum.find;
var replace = findAndReplaceDatum.replace;
var regexEscapedFind = self.escapeRegExp(find);
var regexEscapedReplace = self.escapeRegExp(replace);
var regexEscapedCaseInsensitiveFind = self.makeRegexCaseInsensitive(regexEscapedFind);
// Case insensitive search for the find with a negative lookahead to check its not a case sensitive match of the replacement (aka to check its actually going to make a difference)
var regexString = `(?!${regexEscapedReplace})${regexEscapedCaseInsensitiveFind}`;
do {
// Get the latest version of the text node
textNode = self.getTextNodes($contentEditable)[i];
var text = textNode.data;
var regex = new RegExp(regexString);
var matchIndex = text.search(regex);
var matchFound = (matchIndex !== -1);
if (matchFound) {
// Select the match
var range = document.createRange();
range.setStart(textNode, matchIndex);
range.setEnd(textNode, matchIndex + find.length);
// Delete it
range.deleteContents();
// Create the replacement node
var textNode = document.createTextNode(replace);
// Insert it
range.insertNode(textNode);
// Set the range to the end of the selected node
range.collapse(false);
// Set the user selection the range
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
// Make sure there a no adjacent or empty text nodes
$contentEditable[0].normalize();
}
} while (matchFound)
});
});
});
}
escapeRegExp(string) {
// https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
getTextNodes($contentEditable) {
return $contentEditable.contents().filter(function () {
return this.nodeType == 3; // Text node
});
}
makeRegexCaseInsensitive(string) {
var stringArray = string.split('');
stringArray = stringArray.map(function (char) {
if (char.toLowerCase() !== char.toUpperCase())
return '[' + char.toLowerCase() + char.toUpperCase() + ']';
else
return char;
});
return stringArray.join('');
}
}
div {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(function(){
var findAndReplaceData = [
{
'find': 'find me',
'replace': 'and replace with me!'
},
{
'find': 'foo',
'replace': 'bar'
},
{
'find': 'no',
'replace': 'yes'
}
];
$contentEditable = $('div');
new FindAndReplace($contentEditable,findAndReplaceData);
});
</script>
<div contenteditable="true"></div>
I have a paragraph of text in which the user may place a "pin" to mark a position. Once a pin has been placed, I would like to allow the user to move its position by dragging it to a new location in the paragraph. This is simple to do with block elements, but I have yet to see a good way to do it with inline elements. How might I accomplish this?
I have already implemented it using window.selection as a way to find the cursor's location in the paragraph, but it is not as smooth as I would like.
As a note, I am using the Rangy library to wrap the native Range and Selection functionality, but it works the same way as the native functions do.
Here is the code:
$(document).on("mousedown", '.pin', function () {
//define what a pin is
var el = document.createElement("span");
el.className = "pin";
el.id = "test";
//make it contain an empty space so we can color it
el.appendChild(document.createTextNode("d"));
$(document).on("mousemove", function () {
//get the current selection
var selection = rangy.getSelection();
//collapse the selection to either the front
//or back, since we do not want the user to see it.
if (selection.isBackwards()) {
selection.collapseToStart();
} else {
selection.collapseToEnd();
}
//remove the old pin
$('.pin').remove();
//place the new pin at the current selection
selection.getAllRanges()[0].insertNode(el);
});
//remove the handler when the user has stopped dragging it
$(document).on("mouseup", function () {
$(document).off("mousemove");
});
});
And here is a working demo: http://jsfiddle.net/j1LLmr5b/22/ .
As you can see, it works(usually), but the user can see the selection being made. Have any ideas on how to move the span without showing the selection highlight? I will also accept an alternate method that does not use the selection at all. The goal is to allow movement of the span as cleanly as possible.
You can do this using ranges instead using code similar to this answer. Unfortunately the code is a bit longer than ideal because IE hasn't yet implemented document.caretPositionFromPoint(). However, the old proprietary TextRange object, still present in IE 11, comes to the rescue.
Here's a demo:
http://jsfiddle.net/j1LLmr5b/26/
Here's the relevant code:
var range, textRange, x = e.clientX, y = e.clientY;
//remove the old pin
$('.pin').remove();
// Try the standards-based way first
if (document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
range.collapse();
}
// Next, the WebKit way
else if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
}
// Finally, the IE way
else if (document.body.createTextRange) {
textRange = document.body.createTextRange();
textRange.moveToPoint(x, y);
var spanId = "temp_" + ("" + Math.random()).slice(2);
textRange.pasteHTML('<span id="' + spanId + '"> </span>');
var span = document.getElementById(spanId);
//place the new pin
span.parentNode.replaceChild(el, span);
}
if (range) {
//place the new pin
range.insertNode(el);
}
Try this my friend
el.appendChild(document.createTextNode("d"));
You have create empty span tag that's why you found empty.
add after
el.id = "test";
this
var value = $('.pin').text();
$(el).text(value);
You can hide selection with css
::selection {color:red;background:yellow;}
::-moz-selection {color:red;background:yellow;}
that's all how i can help for a now
Using CodeMirror. I cannot get the getCursor() function to work. I have a jsFiddle with codemirror sources attached.
----> see here JSfiddle <----
I'm trying to insert text into the editor, then force the cursor to move back a specified number of spaces. I'm just trying to get the cursor location with getCursor() but I can't seem to get it to work. Any thoughts?
$(document).ready(function() {
//Changing the textarea to a CodeMirror rich text editor
var editor = CodeMirror.fromTextArea(document.getElementById('theZone'), {
mode: 'text/html',
lineWrapping : true,
lineNumbers : true,
extraKeys : {
"Tab": "indentMore",
"Shift-Tab": "indentLess",
"'>'": function(cm) { cm.closeTag(cm, '>'); },
"'/'": function(cm) { cm.closeTag(cm, '/'); }
} ,
onCursorActivity: function(cm) {
cm.setLineClass(hlLine, null, null);
hlLine = cm.setLineClass(cm.getCursor().line, null, "activeline");
}
});
//When SELECT changes - insert the value into the CM editor, set focus, get cursor position, move cursor back [x] amount of spaces.
$('#sel').change(function() {
var selected = $(this).find('option:selected');
var mynum = selected.data('val');
editor.replaceSelection($(this).val(), focus);
editor.focus();
var start_cursor = editor.getCursor(); //I need to get the cursor position
alert(start_cursor); //Cursor position always comes up [object Object]
//write code to move cursor back [x] amount of spaces. [x] is the data-val value.
});
});
The code seems to work just fine. alert() will not display objects. use console.log() instead.
I added the rest of the code.
$('#sel').change(function() {
var selected = $(this).find('option:selected');
var mynum = selected.data('val');
editor.replaceSelection($(this).val(), focus);
editor.focus();
var start_cursor = editor.getCursor(); //I need to get the cursor position
console.log(start_cursor); //Cursor position
var cursorLine = start_cursor.line;
var cursorCh = start_cursor.ch;
//Code to move cursor back [x] amount of spaces. [x] is the data-val value.
editor.setCursor({line: cursorLine , ch : cursorCh -mynum });
});
The onCursorActivity was not working for me.
This is what worked for me:
let myCodeMirror = CodeMirror.fromTextArea(myTextArea, {
lineNumbers: true,
});
CodeMirror.on(myCodeMirror, "cursorActivity", (instance, obj)=>{
console.log(instance.doc.getCursor())
}
This will log an object every time the cursor changes it's position which contains line number and a ch number.