I am new to javascript and I am trying to start a very simple project which is to display a controllable div that can be moved around using a,w,s,d keys on keyboard. I am currently having problem on how to move around the div because I do not know what attribute to change.
divBar = null;
function detectKey() {
//97 = a
//115 = s
//100 = d
//119 = w
if (event.charCode == 97) {
//a
alert(divBar.position);
}
if (event.charCode == 115) {
//s
}
if (event.charCode == 100) {
//d
}
if (event.charCode == 119) {
//w
}
}
function createDiv() {
divBar = document.createElement("div");
divBar.id = "divBar";
divBar.style.border = "solid 1px #AAAAAA";
divBar.style.backgroundColor = "black";
divBar.style.top = 400;
divBar.style.height = "10px";
divBar.style.width = "100px";
divBar.style.position = "absolute";
document.body.appendChild(divBar);
document.addEventListener("keypress", detectKey, false);
}
I am not sure to put in that condition statement. so that the div will move to the left, right, up and down.
If it's absolutely positioned (which it appears to be), then you change divbar.style.top and divbar.style.left to move it around.
Here's a working example: http://jsfiddle.net/jfriend00/rRbZz/.
Since it's absolutely positioned, you are most likely going to want to change it's top/left style attributes. It will involve reading the current top/left attributes, and then adding/subtracting from them after a a,w,s,d key is pressed, and then adding that value back. So for each key/direction, you are going to have to figure out what impact that will have on the element. will it move it up/down (affect the top attribute) or left/right (affect the left attribute). Read what's there, make the appropriate calculations, and the update the attribute. The trickiest part IMO is reading the initial style.top/style.left attributes, but since you are setting them with Javascript, then you shouldn't have a problem.
Related
Lets say I add a text box of length of 50px, And I want to count the exact number of characters (including whitespace) that perfectly fits inside that text box, I mean no character should be allowed to be typed inside the textbox that require the sliding of whole line toward left; I mean, in another other-words, we need to disallow the typist to further insert any letter as the line reaches up to the length of the text box. Can we anyhow solve this by JavaScrip? Thanks for the help in advance, any help would be appreciated.
The whole logic is flawed as it would depend also on the size of the text inside the input. I'd put instead a limit of chars to be entered that don't go beyond. Using maxlength input attribute.
Anyways if you really wanna go this route, which I think is an overkill and not needed, then you can:
Make use of CanvasRenderingContext2D.measureText, docs here
In order to do that you'd have to create a hidden canvas element where to mimic your input text.
After that you will need to check on input if the text goes beyond the input width and avoid any further keystrokes but still allow for deletion.
Find attached an example snippet, not optimised, of what I am talking about.
const form = document.querySelector('#form'),
input = form.querySelector('input')
const createAppendCanvas = form => {
const canvas = document.createElement('Canvas')
form.appendChild(canvas)
}
createAppendCanvas(form)
const getTextMetrics = inputText => {
const canvas = document.querySelector('canvas'),
textWidth = Math.ceil(canvas.getContext('2d').measureText(inputText).width) + 10
return textWidth
}
const disableTyping = (event, input) => {
const inputText = event.target.value,
inputWidth = input.clientWidth
if (getTextMetrics(inputText) >= inputWidth) {
event.preventDefault()
return false
}
}
input.addEventListener('keypress', event => disableTyping(event, input))
input {
width: 50px;
}
canvas {
display: none;
}
<form id="form">
<input type="text" />
</form>
As #mel-macaluso rightly points out, this is a very big rabbit hole to go down, and the standard practice is to use the maxlength attribute to limit the number of characters.
*Edit: You can also set the width of the input using em, which is proportional to the font size. (The name em was originally a reference to the width of the capital M in the typeface and size being used, which was often the same as the point size ref) A combination of width in em and maxlength will give a very rough approximation of what you may be trying to achieve.
However if you really want to be able to limit input based text length, this would serve as a very simplistic example of how you might get started.
Edit: I recommend #mel-macaluso's answer: he added an example using CanvasRenderingContext2D.measureText(), which I suspect is much more efficient than getBoundingClientRect.
First some disclaimers:
This example doesn't take into account clipboard actions. That's a pretty big problem, and you'd be talking a lot more code to try to account for it (way beyond the scope of what can reasonably be done here).
It's also rather resource intensive. The process doing a getBoundingClientRect, forces the browser to reflow the document contents an extra time. Depending on the size of the page this can be a big deal, and it's not something to be done lightly.
var inp = document.getElementById('test');
// get font for input
var style = getComputedStyle(inp);
var maxWidth = inp.getBoundingClientRect().width;
var sizeTest = document.createElement('span');
// set font for span to match input
sizeTest.style.font = style.font;
inp.addEventListener('keydown', function(e){
if(e.ctrlKey || e.altKey) return;
if(e.key && e.key.length===1) {
sizeTest.textContent = inp.value;
document.body.append(sizeTest);
var w = sizeTest.getBoundingClientRect().width;
sizeTest.remove();
console.log(maxWidth, w, e.key, e.code);
if(w>maxWidth) e.preventDefault();
}
})
<input id='test'/>
So why is it so complex to do something like this? Fonts are tricky things. You have variable width (proportional) fonts, kerning, ligatures, etc. It's very complex, and browsers don't provide access to most of this information.
So if you want to know how long a segment of text is, you generally have to put it in a span with the same font settings and then request the bounding dimensions.
Here's neat solution using nested spans (with a contenteditable inner span) as a proxy input.
// Identifiers and dynamic styling
const innerSpan = document.querySelector("span.inner"),
outerSpan = document.querySelector("span.outer");
/* Threshold should be at least one character-width less than outerSpan.
(This formula was pretty close for my few tests;
for more precision and less flexibility, you can hard-code a value.) */
const estMaxCharWidth = innerSpan.offsetHeight / 1.7,
thresholdWidth = outerSpan.offsetWidth - estMaxCharWidth;
innerSpan.style.minWidth = `${Math.floor(thresholdWidth)-3}px`; // defaults to 0
innerSpan.style.minHeight = `${Math.floor(outerSpan.offsetHeight)-2}px`
// Listeners
innerSpan.addEventListener("focus", customOutline);
innerSpan.addEventListener("keydown", checkKeyAndWidth);
innerSpan.addEventListener("blur", removeOutlineAndHandleText);
// Functions
function checkKeyAndWidth(e){
// Runs when user presses a key, Conditionally prevents input
if(e.code == "Enter" || e.keyCode == 13){
e.preventDefault(); // Don't insert a new line
e.target.blur(); // (In production, set the focus to another element)
}
else{
// Some keys besides Enter are important, More could be added
const whitelistCodes = ["Backspace", "Tab", "Escape", "ArrowLeft", "ArrowRight", "Insert", "Delete"];
const whitelistKeyCodes = [8,9,27,37,39,45,46];
// If the inner span is wide enough, stop accepting characters
let acceptingCharacters = e.target.offsetWidth <= thresholdWidth;
if(!acceptingCharacters && !whitelistCodes.includes(e.code) && !whitelistKeyCodes.includes(e.keyCode) && !whitelistKeyCodes.includes(e.which)){
// Unauthorized incoming keystroke
e.preventDefault();
}
}
}
function customOutline(){
// Runs when span gets focus, Needed for accessibility due to CSS settings
outerSpan.style.borderColor = "DeepSkyBlue";
}
function removeOutlineAndHandleText(){
// Runs when focus is lost
outerSpan.style.borderColor = "Gray";
if(innerSpan.length < 1){ innerSpan.innerHTML = " "; } // force content
/* Since this is not a real input element, now might be the time to do something with the entered text */
}
.outer{
display: inline-block;
position: relative;
width: 100px; /* Defaults to 0 */
padding: 0;
border: 1px solid gray;
}
.inner{
display: inline-block;
position: relative;
top: 0;
height: 100%;
margin: 0;
outline: none; /* Don't do this without calling customOutline on focus */
}
<!-- requires that browser supports `contenteditable` -->
<span class="outer">
<!-- space character in innerSpan may improve cross-browser rendering -->
<span class="inner" contenteditable="true"> </span>
</span>
I need to create a function checking if slider is in the certain left position. Depending on its position, I need to change another element's class. And it needs to be working every time slider's left position changes.
Everything works for me except the every time feature ;). Setting up setTimeout didn't help either...
Here's my code:
var $slider_pos = jQuery('.contentHolder').css('left');
var $slider_pos = $slider_pos || 0;
var $my_window = jQuery(window).width();
var $sensor = jQuery('.sensor');
function slideCheck(){
if($slider_pos == 0){
$sensor.addClass('red');
} else if($slider_pos == $my_window * -1){
$sensor.addClass('blue');
$sensor.removeClass('red');
}
setTimeout(slideCheck,500);
}
slideCheck();
Your help would be very appreciated for I've been strugling with this one for some time now.
Thank you in advance!
I am currently running a script on a third-party page to determine the maximum dimensions of the page. At first, this may seem as if I could just use outerWidth() and outerHeight() on my parent wrapper, #base but the problem is that the page wrapper isn't sized from its children. So I might have a parent that is 0x0 and its child is 400x400 and a child inside of that which is 500x500. It seems they just allow overflow. I have tried some CSS tricks in attempt to force the parent #base to size itself correctly, but the children don't seem to drive this change and modifying their CSS causes actual alignment issues on the page. Additionally, there are many hidden items on the page that do not become visible until later or during page interaction so this further prevents me from just grabbing the outer dimensions of #base or something like that.
My current approach is to iterate through every single element on the page. I check to see where it is positioned and what its dimensions are. Based on those, I update my maximum dimensions. I also have to check for horizontal and vertical scrolling elements because those may be on the page too. If a wrapper is 500px wide and the child has a width of 1000px, but is scrolled, I wouldn't want that to affect my maximum dimensions. Anyways, this approach works but it's slow. Sometimes the page may have +15k elements. With these numbers, it takes 10 seconds on my machine. I might be able to optimize some of the conditional statements to use booleans instead of evaluating values, but I don't think this will make a significant difference. I'm hoping there is some process I'm completely overlooking. Below is my current code snippet and a demo showing how the page looks prior to running the code and after the code has been run.
Demo: https://p826ni.axshare.com/#g=1&p=without_code
$('#base *').not('script, style').each(function () {
currentElement = $(this);
// Initialize on first loop.
if (parentElementHorizontal === undefined && parentElementVertical === undefined) {
parentElementHorizontal = currentElement;
parentElementVertical = currentElement;
}
width = currentElement.outerWidth();
height = currentElement.outerHeight();
scrollWidthHidden = currentElement[0].scrollWidth;
scrollHeightHidden = currentElement[0].scrollHeight;
top = currentElement.offset().top;
left = currentElement.offset().left;
// Check if we're still within the parent containing horizontal-scrolling overflow.
if (!$.contains(parentElementHorizontal[0], currentElement[0])) {
hiddenWidth = false;
}
// Check if we're still within the parent containing vertical-scrolling overflow.
if (!$.contains(parentElementVertical[0], currentElement[0])) {
hiddenHeight = false;
}
// Check if we've found an element with horizontal-scrolling content.
if (!hiddenWidth) {
maxWidth = maxWidth < left + width ? left + width : maxWidth;
} else if (currentElement.width() > maxWidth) {
currentElement.addClass('redline-layer');
}
if (scrollWidthHidden > width && !hiddenWidth && width > 0) {
hiddenWidth = true;
parentElementHorizontal = currentElement;
}
// Check if we've found an element with vertical-scrolling content.
if (!hiddenHeight) {
maxHeight = maxHeight < top + height ? top + height : maxHeight;
} else if (currentElement.height() > maxHeight) {
currentElement.addClass('redline-layer');
}
if (scrollHeightHidden > height && !hiddenHeight && height > 0) {
hiddenHeight = true;
parentElementVertical = currentElement;
}
});
Hello I want to make a Menu thats pops out of the side when I click a button. I have it all set up with the CSS but the Javascript part doesn't work.
I want to test if the width of menubarWrapper is equal to 300 then the width of the menubarWrapper needs to change to 0px and if it isn't equal to 300px than change it to 300px.
I have the following JS:
function menuBarToggle() {
var menuWrapper = document.getElementById('menuWrapper');
if menuWrapper.width == 300 {
menuWrapper.style.width = "0";
} else {
menuWrapper.style.width = "300";
}
}
I also tried in the IF statement menuWrapper.style.width but that doesn't work also
There are other answers that are just fine --
use "300px", not "300"
Surround your conditional with a parentheses.
(You'll need both, by the way.)
But I wanted to make sure that somewhere on this page, someone pointed out that this is a very brittle way of toggling. You have a magical, hardcoded integer for the size, which might break if you ever wanted to style things differently. And if you decide one day to fade out the menu, then the test won't work at all.
Might I suggest that, instead, you create two classes in CSS:
.menu-item { width: 300px; }
.menu-item.collapsed { width: 0; }
And then in javascript, you'll only have to write the following:
function menuBarToggle() {
var menuWrapper = document.getElementById('menuWrapper');
menuWrapper.classList.toggle('collapsed');
}
Not only is the intention easier to read, but this will allow you to swap out the behavior if you decide that, instead of purely narrowing the menu, you might want it to fade out, or animate it to the left, or... well... whatever can come up with.
Your script has a typo. Add '()' for an if statement.
function menuBarToggle() {
var menuWrapper = document.getElementById('menuWrapper');
if (menuWrapper.width == 300) {
menuWrapper.style.width = "0";
} else {
menuWrapper.style.width = "300";
}
}
When changing the width of an element via style.width, you have to append px to the end of the string:
function menuBarToggle() {
var menuWrapper = document.getElementById('menuWrapper');
if menuWrapper.width == 300 {
menuWrapper.style.width = "0px";
} else {
menuWrapper.style.width = "300px";
}
}
Is there a way to get elements which is:
Inside a div with overflow: scroll
Is in viewport
Just like the following picture, where active div (5,6,7,8,9) is orange, and the others is green (1-4 and >10) :
I just want the mousewheel event to add "active" class to div 5,6,7,8,9 (currently in viewport). View my JSFiddle
$('.wrapper').bind('mousewheel', function (e) {
//addClass 'active' here
});
You could do something like this. I would have re-factored it, but only to show the concept.
Firstly I would attach this to scroll event and not mousewheel. There are those among us that likes to use keyboard for scrolling, and you also have the case of dragging the scrollbar. ;) You also have the case of touch devices.
Note that with this I have set overflow:auto; on wrapper, thus no bottom scroll-bar.
With bottom scrollbar you would either have to live with it becoming tagged as in-view a tad to early, or tumble into the world of doing a cross-browser calculating of IE's clientHeight. But the code should hopefully be OK as a starter.
»»Fiddle««
function isView(wrp, elm)
{
var wrpH = $(wrp).height(),
elmH = $(elm).height(),
elmT = $(elm).offset().top;
return elmT >= 0 &&
elmT + elmH < wrpH;
}
$('.wrapper').bind('scroll', function (e) {
$('div.box').each(function(i, e) {
if (isView(".wrapper", this)) {
$(this).addClass('active');
} else {
$(this).removeClass('active');
}
});
});
Note that you should likely refactor in such a way that .wrapper height is only retrieved once per invocation, or if it is static, at page load etc.
Update; a modified version of isView(). Taking position of container into account. This time looking at dolphins in the pool.
»»Fiddle««
function isView(pool, dolphin) {
var poolT = pool.offset().top,
poolH = pool.height(),
dolpH = dolphin.height(),
dolpT = dolphin.offset().top - poolT;
return dolpT >= 0 && dolpT + dolpH <= poolH;
}