Ensure an element has a proper offset from another element? - javascript

I'm not exactly sure how to better phrase the title above so if you can think of a more clear and concise title feel free to re-phrase it.
I am creating a generic tool-tip with HTML, CSS, and Javascript (no jQuery, Angular, etc.). I've made it to a point where everything looks pretty clean, until I resize the window and scroll down. Now, from my understanding, mobile devices do not have an onmouseover or onmouseout event and therefore I do not need to account for this; however, if I take into account the smaller screens of some laptops (I've seen 10 inch screens) then this could be an issue.
When I hover over one of the elements (they are all a elements in this example), the tool-tip appears as it should, however when I resize as I described above, the onmouseout event is firing because the mouse is technically over the tool-tip, not the element anymore. I thought that offsetting from the position of the element by its height plus five pixels that I would be safe from that issue, but it seems that the position isn't the position I'm expecting.
I am currently using getBoundingClientRect() to calculate the positions but I believe this is based on the view port position while I think I'm needing based on the position in the page instead that way as the page moves so does where the tool tip is. I've included my code below and you can test it out and see what I'm talking about.
var tooltip = document.getElementById("globalTooltip");
function showTooltip(sender) {
var senderBounds = sender.getBoundingClientRect();
var top = senderBounds.y + senderBounds.height + 5;
tooltip.dataset.owner = sender;
tooltip.innerHTML = sender.dataset.tooltip;
tooltip.classList.add(sender.dataset.tooltiptype);
if (top + tooltip.getBoundingClientRect().height + 5 > window.innerHeight)
top = senderBounds.y - tooltip.getBoundingClientRect().height - 5;
tooltip.style.top = top + "px";
tooltip.style.left = (senderBounds.x + 5) + "px";
tooltip.classList.add("active-tooltip");
}
function hideTooltip(sender) {
tooltip.style.left = "-250px";
tooltip.classList.remove("error");
tooltip.classList.remove("warning");
tooltip.classList.remove("success");
tooltip.classList.remove("neutral");
tooltip.classList.remove("active-tooltip");
}
body {
margin: 0;
text-align: center;
}
.disabled-link {
color: #777;
cursor: default;
}
li {
padding: 5px;
list-style-type: none;
}
.tooltip {
position: absolute;
padding: 10px;
cursor: default;
border-radius: 25px;
color: #333;
opacity: 0;
transition: all 0.2s ease-in-out;
max-width: 30vw;
}
.neutral { background-color: #ddd; }
.success { background-color: #7c7; }
.warning { background-color: #fc3; }
.error { background-color: #f55; }
.active-tooltip { opacity: 1; }
<div class="container">
<ul>
<li>
<a class="disabled-link"
data-tooltip="This link is available in another location."
data-tooltiptype="warning"
onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">
Hover Over This Disabled Link</a>
</li>
<li>Some Active Link</li>
<li>
<a class="disabled-link"
data-tooltip="Something great is coming soon!"
data-tooltiptype="success"
onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">
Hover for more details...</a>
</li>
<li>Some Active Link</li>
<li>
<a class="disabled-link"
data-tooltip="This area is currently under maintenance."
data-tooltiptype="error"
onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">
Another Disabled Link</a>
</li>
<li>Some Active Link</li>
<li>Some Active Link</li>
<li>Some Active Link</li>
<li>
<a class="disabled-link"
data-tooltip="This is an example of what happens when the text supplied to the tooltip is rather verbose instead of the enjoyable concise messages that are typically used to relay information instead. No one likes to read a novel in a tooltip, but hey, it's not my decision, it just has to be accounted for due to an excessive and old page."
data-tooltiptype="neutral"
onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">
Really Verbose Message</a>
</li>
</ul>
</div>
<i id="globalTooltip" data-owner="none" class="tooltip"></i>
How can I ensure the tool-tip is always offset from the element instead of flying in over it?

Related

Drop Down Menu Items Not in Tab Order

I am working on a web design project and have run into an issue with the navigation menu. Our main site is created using WordPress and the main menu contains drop down menus which one can navigate using the tab key. In other words, the drop down menu opens upon tabbing over the top-level item and one can navigate through that menu with the tab key. In order to ensure a consistent and accessible user experience I need to recreate this on an outside platform (not WordPress) using HTML, CSS, and Javacript.
Using the code below I have created a drop down menu with the correct look and feel. Utilizing two event handlers accommodates both people using the mouse and those using a keyboard (I am less knowledgeable about how screen readers would handle this). Unfortunately I can't seem to tab through the sub-menus, only the top level items ("About", "Accounts", etc.) and am not entirely sure why. One would think just adding tabindex="0" would put the items in question into the tab order, but that doesn't seem to be the case. This doesn't seem to be an hugely common problem because I have had only limited success when searching both Google and Stackoverflow. Does anyone have any insights into the issue?
//For desktop menu
var customMenuHeaders = document.getElementsByClassName('TopMenuItem');
var customMenuItems = document.getElementsByClassName('NavMenuItem');
//Makes the desktop drop down menus visible upon focus
for (let i = 0; i < customMenuHeaders.length; i++){
customMenuHeaders[i].addEventListener('focusin', function(event){
for (let j = 0; j < customMenuItems.length; j++){
customMenuItems[i].style.display = "block";
}
});
customMenuHeaders[i].addEventListener('mouseover', function(event){
for (let j = 0; j < customMenuItems.length; j++){
customMenuItems[i].style.display = "block";
}
});
customMenuHeaders[i].addEventListener('focusout', function(event){
for (let k = 0; k < customMenuItems.length; k++){
customMenuItems[i].style.display = "none";
}
});
customMenuHeaders[i].addEventListener('mouseout', function(event){
for (let k = 0; k < customMenuItems.length; k++){
customMenuItems[i].style.display = "none";
}
});
}
.navigation {
font-family: montserrat;
font-weight: bold;
float: right;
font-size: 14px;
text-align: right;
width: 60%;
position: absolute;
left: 450px;
z-index: 100;
clear: left;
}
.TopMenuItem {
display: inline-block;
vertical-align: top;
background-color: #FFFFFF;
margin-left: 25px;
}
.TopMenuItem:hover {
text-decoration: underline;
}
.TopMenuItem a {
color: #000000;
}
.NavMenuItem {
display: none;
text-align: left;
border-top: 2px solid #FFD200;
background-color:#FFFFFF;
width: 250px;
padding-right: 50px;
padding-left: 50px;
padding-top: 20px;
padding-bottom: 20px;
overflow: visible;
position: absolute;
z-index: 500;
}
.NavMenuItem li {
list-style-type: none;
padding-top: 5px;
padding-bottom: 5px;;
}
.NavMenuItem li a {
color: #000000;
}
<nav class="navigation" aria-label="main_navigation" role="navigation">
<ul >
<li class="TopMenuItem">About
<ul class="NavMenuItem">
<li>About the Library</li>
<li>Visiting the LIbrary</li>
<li>Contact Us</li>
<li>Faculty Info</li>
<li>Maps and Spaces</li>
</ul>
</li>
<li class="TopMenuItem">Accounts
<ul class="NavMenuItem">
<li>Interlibrary Loan</li>
<li>Renewals</li>
<li>Refworks</li>
<li>Skidmore eMail</li>
<li>The Spring</li>
</ul>
</li>
<li class="TopMenuItem">Services
<ul class="NavMenuItem">
<li>Borrowing</li>
<li>Course Reserves</li>
<li>Interlibrary Loan Info</li>
<li>Library Instruction</li>
<li>Technology</li>
</ul>
</li>
<li class="TopMenuItem">Resources
<ul class="NavMenuItem">
<li>Citing Sources</li>
<li>Creative Matter</li>
<li>Digital Collections</li>
<li>Images</li>
<li>Movies</li>
<li>Music</li>
<li>Popular Reading</li>
<li>Primary Sources</li>
<li>Reference Sources</li>
</ul>
</li>
<li class="TopMenuItem">Under Our Roof
<ul class="NavMenuItem">
<li>GIS Center</li>
<li><a href="https://lucyscribnerlibrary.reclaim.hosting/special-collections/Special Collections</li>
<li>IT Help Desk</li>
<li>LEDS</li>
<li>MDOCS</li>
<li>Media Services</li>
<li>Writing Center</li>
</ul>
</li>
</ul>
</nav>
Got it! After reading the comment by Scott Marcus I took another look at the code. The issue was that the menu items were effectively invisible to the computer since the default display setting was none. So even once the JavaScript changed the display to block, the child items of the top level li tag were still invisible and thus not accessible via keyboard.
What worked was to change my approach to the menu's visibility status. Following on a post from WebAIM, I absolutely positioned the drop down menus off screen and used the script to move them into place when either a focus or mouseover event occurs. On the surface the effect is the same, but since the content is always technically visible to the computer there is no longer any problem with not being able to tab through the menu.

sub-menu expansion and innerHTML replacement based on onclick, using just Javascript

First, I should mention that I'm about 4-weeks new to the coding world, and this is the first time I'm trying to make (what I thought would be) a simple site.
I have seen many similar questions on Stack Overflow, but in trying to adapt the code samples provided in the solutions, the solution would stop working.
So, the current hurdle is:
I have a menu defined in HTML with a sub-menu in one of the <li> elements ("Portfolio"), and that <li> element contains the character ▼ (&#9660).
I set up an onclick event for that <li> element so that when it was clicked it would do two things: expand/display the sub-menu <li> elements directly below it (pushing the other <li> elements in the menu further down), and replace the ▼ character with a ▲ character (&#9650)... until the <li> element was clicked again to shrink/hide the sub-menu.
I'm not sure if it matters, but this menu is inside a grid item because the page is set up using CSS Grid.
So basically:
HOME
ABOUT US
PORTFOLIO ▼
INFORMATION
CONTACT
...would become:
HOME
ABOUT US
PORTFOLIO ▲
LINK 1
LINK 2
LINK 3
INFORMATION
CONTACT
No matter how I set up my classes and IDs, I cannot get the arrow symbol to swap, and somewhere along the line, I messed up the coding and now the sub-menu doesn't even expand anymore.
It's likely embarrassingly bad code (given that I've tried to mash together bits from samples I've seen) but here is what I have. Thanks in advance.
var arrowstring = document.getElementById("arrowdirection").innerHTML;
document.getElementById("IDforPortfolioLink").classList.toggle("show");
if (IDforPortfolioLink.classList.contains('show')) {
arrowstring = "&#9650"
} else {
arrowstring = "&#9660"
}
arrowdirection.textContent = arrowstring;
}
.sub-menu-content {
display: none;
position: absolute;
}
.sub-menu-content a {
display: block;
}
.sub-menu-content a:hover {
background-color: green;
}
.show {
display: block;
}
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<ul class="menu">
<li>Home</li>
<li>About us</li>
<li class="LinkForPortfolio" id="IDforPortfolioLink" onclick= "myFunction()">LINK <span class="arrow" id= "arrowdirection">▼</span><div class="sub-menu-content" id="myportfolio">
<ul>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ul>
</div>
</li>
<li>Information</li>
<li>Contact</li>
</ul>
</body>
</html>
Well, your code had so many flaws I had to rewrite it.
I'll explain everything that I possibly can of what I did here:
I changed the HTML a bit: I have added div's, instead of ul's with li's, inside a nav(container). It's more indicated to do so because it keeps the markup clean, and is less harder to debug.
I have assigned nav a display: flex; justify-content: center; align-items: center; which centers the divs inside nav, and inlines them. I did so with nav div, which pretty much centered the text inside of them.
I have removed all of the classes expect of .portfolio because it's useless to have that many classes.
I made div.expand-portfolio a child of div.portfolio, which in itself(.expand-portfolio) has another ul child, which holds the links. You might've noticed that I've added .portfolio a position: relative; and .expand-portfolio a position: absolute;. I did that because, I wanted to take .expand-portfolio out of the document flow, which basically means I wanted to make .expand-portfolio not interact with any element on the page. Now, when assigning position: absolute; to a child inside a container, the child's position is going to be relative to the document and not the parent. This is why you may add position: relative; to the parent.
I created a separate class called .expanded which gives .expand-portfolio a height of 150px when assigned to it.
You also might have noticed I gave the divs inside the nav a transition: 500ms ease, what that does is make the transition between the properties smooth, and not sudden. You may remove that property from them if you don't want that.
Now, the javascript.
When I made those 3 variables, which are the references of the elements from the page, you noticed I used document.getElementsByClassName followed by a [0]. What document.getElementsByClassName() returns is: a nodelist. Documentation here. It's basically a sort of "array", and with [0] appended to it, I select only the first and only element of the page with that class.
You may have observed I added the onclick function in the javascript file. Personal preference. I said that when I click the portfolio button, first, you should change that span's innerHTML. (the span element holds the actual symbol). I also said you should toggle the .expanded class. And, I made an if statement, checking if .expand-portfolio doesn't contain the class. If it doesn't, you can pretty much see what it does.
I hope it helps. If you have any more questions, ask them in the comments.
var portfolio = document.getElementsByClassName("portfolio")[0];
var portfolioInner = document.getElementsByClassName("inner-html")[0];
var expandPortfolio = document.getElementsByClassName("expand-portfolio")[0];
portfolio.onclick = function(){
portfolioInner.innerHTML = "▲";
expandPortfolio.classList.toggle("expanded");
if(!expandPortfolio.classList.contains("expanded")){
portfolioInner.innerHTML = "▼"
}
};
html, body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
.menus-container {
height: 100%;
width: auto;
}
.menus-container > div {
padding: 10px 10px 5px;
transition: 500ms ease;
width: 30%;
height: 100%;
cursor: pointer;
}
.expand-portfolio {
overflow: hidden;
width: 100px;
height: 0;
background-color: #000;
transition: 500ms ease;
}
.expand-portfolio ul {
padding-left: 25px;
}
.expand-portfolio ul li {
padding: 10px 0 10px 0;
color: #fff;
}
.portfolio span {
margin-left: 5px;
}
.expanded {
height: 150px;
}
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="menus-container">
<div>Home</div>
<div>About Us</div>
<div class="portfolio">Portfolio <span class="inner-html">&#9660</span>
<div class="expand-portfolio">
<ul>
<li>LINK 1</li>
<li>LINK 2</li>
<li>LINK 3</li>
</ul>
</div>
</div>
<div>Information</div>
<div>Contact</div>
</div>
</body>
</html>

Center any element on a sliding horizontal line

I have a horizontally sliding line of elements within a fixed width, so you have to scroll left an right to see all the elements. See JS Fiddle and text-only example below.
Hi | Hello | How do you do | Fine thanks | Good weather this time of year
My question is: How can I center any given element horizonally? Eg. Put the horizontal center of element number 3 in the horizontal center of the surrounding div.
If the element can't be centered because it is at the beginning of the line for example, that's OK, then the H-position should simply be 0. It should only be centered as much as it can be.
So something like this? You can use a combination of the following native properties scrollLeft, offsetLeft, and offsetWidth.
var items = document.querySelectorAll('.wrapper ul li');
function centerItems(which) {
var wrapper = items[which].offsetParent;
wrapper.scrollLeft =
(items[which].offsetLeft + items[which].offsetWidth / 2)
- wrapper.offsetWidth / 2;
}
html {
background: #eee;
}
body {
width: 320px;
background: white;
font-family: sans-serif;
}
.wrapper {
overflow-x: scroll;
position: relative;
}
ul {
white-space: nowrap;
}
li {
display: inline-block;
color: #898;
background: #efe;
padding: 8px;
}
<p>Hello!</p>
<button onclick="centerItems(0)">1</button>
<button onclick="centerItems(1)">2</button>
<button onclick="centerItems(2)">3</button>
<button onclick="centerItems(3)">4</button>
<button onclick="centerItems(4)">5</button>
<div class="wrapper">
<ul>
<li>Short</li>
<li>Very long line here</li>
<li>Medium line</li>
<li>Another one Another one Another one</li>
<li>Yet another</li>
</ul>
</div>
<p>Goodbye!</p>

Variable Width Media Queries [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I have done quite a bit of digging, and can't seem to find how people handle content restructuring for a variable width element.
For example, if I have a dynamically created horizontal menu it may only have 3 items..
<div>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</div>
And this menu will only have a small width, let's say 400px. I can create a media query to adjust the way it is displayed when the window falls below 400px, however..
If a user adds another item..
<div>
<ul>
<li>Home</li>
<li>About</li>
<li>Location</li>
<li>Contact</li>
</ul>
</div>
Suddenly this menu is larger then 400 px, and so on. My question is, how can I structure my code to handle a variable element width and still control the way that is displayed?
EDIT: When I re-size the browser window on my horizontal menu, at a certain variable width, the inline-block li elements drop below the rest of the menu. Instead of letting each element drop as the screen is compressed I would prefer to make the entire menu drop to a vertical orientation. I cannot simply use a media query, since there are variable amounts of menu items. To illustrate the issue try re-sizing the example code in this fiddle: https://jsfiddle.net/f5Lv73hp/
I don't understand your question, so, consider editing your post with more information, including what do you espect...
By the way:
Horizontal Menu, if you need to keep all list-items with the same width, you can use display-table, there aren't any javascript requirements, just set the list as a table ( see .menu-horizontal css ).
function CasesCtrl($) {
var case1 = $('#case1');
$('button', case1).click(function() {
var list = $('ul', case1);
var len = $('li', list).length;
var newItem = '' +
'<li class="menu-item">' +
'<a class="menu-item-link">Item '+ (len + 1) +'</a>' +
'</li>'
;
list.append(newItem);
});
}
jQuery(document).ready(CasesCtrl);
article {
width: 100%;
padding: 2px;
border: 1px solid black;
margin-bottom: 2em;
overflow: hidden;
}
.menu {
list-style: none;
margin: 0;
padding: 0;
}
.menu-item {
}
.menu-item-link {
background: lightseagreen;
margin: 2px;
padding: 2px 5px;
display: block;
text-transform: uppercase;
line-height: 1;
}
.menu-horizontal {
display: table;
}
.menu-horizontal .menu-item {
display: table-cell;
width: 1%;
white-space: nowrap;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<article id="case1">
<ul class="menu menu-horizontal">
<li class="menu-item">
Item 1
</li>
<li class="menu-item">
Item 2
</li>
<li class="menu-item">
Item 3
</li>
</ul>
<button type="button">Add Menu Item</button>
</article>
Be more specific and I'll edit my answer as you need!
Are you looking for something like this? https://jsfiddle.net/4p18mxg9/2/
I am using javascript to get the width of the ul and applying the width to to media query, that way when you add more li it is not dependent on the content.
var width = document.getElementById('ul').offsetWidth;
document.querySelector('style').textContent +=
"#media screen and (max-width:" + width + "px) { li{float: none; width: 100%; background-color: blue; color: white;}}"
Added some color, to see easier: https://jsfiddle.net/4p18mxg9/3/

Span and list elements wrap in DIV

I have tried to combine a span element and a list that uses in-block formatting. There seems to be so misalignment of the elements.
Also, would anyone know how to wrap the elements within a fixed width?
Her's a link
<http://jsfiddle.net/joewaldronrit/3nhdnbL8/#&togetherjs=97QmIzvPKD>?
CSS:
.word-sugg-hint{
position:absolute;
top: 50px;
text-align: left;
font-size: 12px;
padding-right: 0px;
color:rgb(32,106,138);
}
.sugg-details{
display:inline-block;
text-align:left;
}
ul.suggestion-list li{
display: inline-block;
font-size: 14px;
height:0px;
}
ul.suggestion-list{
display: inline-block;
font-size: 12px;
margin-left: 0px;
padding-bottom:3px;
}
ul.suggestion-list li:hover{
color:rgb(105, 131, 73);
cursor:pointer;
text-decoration: underline;
}
ul.suggs.suggestion-list li{
/*
width:180px;
overflow:hidden;
text-overflow: ellipsis;
white-space: nowrap;
*/
float: left;
height: 20px;
color:#0000FF;
font-size:14px;
display:inline-block;
padding:0px;
}
Javascript:
var crateItems = ["apples", "bananas", "grapefruit"];
var suggList = document.getElementById("suggestion-list");
for (var i = 0; i < suggList.children.length; i++) {
if (crateItems.length === i) break;
suggList.children[i].innerHTML = crateItems[i] + (i < crateItems.length - 1 ? "," : "");
}
HTML
<div class="word-sugg-hint" id ="sugg-div">
<h class="sugg-details"> Did you mean? </h>
<ul id="suggestion-list" class="suggestion-list suggs">
<li>
</li>
<li>
</li>
<li>
</li>
<li>
</li>
<li>
</li>
<li>
</li>
<li>
</li>
<li>
</li>
<li>
</li>
<li>
</li>
</ul>
</div>
This answer assumes that instead of an <h> element, you mean <span>.
The main reason your elements were misaligned was your float:left was clashing with your display: inline-block. That solved your first problem. Your second question, how to wrap elements within a fixed width. If you see my jsFiddle, I simply wrapped the span and ul with a div that had a class called 'wrap'. I gave that element a fixed width. I also gave it a background color so you can easily see the width of each div. Because div's are block elements, I had to make it display as inline-block. You will see the text is aligned, and the widths are the same. Take away the background color, and I think it renders the way you wish: http://jsfiddle.net/3nhdnbL8/2/
As a greater take away, may I suggest, when working with CSS to get the desired look, too often we keep adding stuff. It is important to remember that when you add something, that may clash with something that already exists. Each time you want to add something, I would first ask if there is anything you should take out. It is a good idea to plan our CSS the same way we plan our JavaScript. Too much CSS causes a great deal of conflicts, and can become very difficult to debug.
Good luck.

Categories

Resources