I have a div with an icon and a message which is hidden unless someone mouses over it, that does an action when clicked.
For sighted users, the icon switches to a check mark when clicked, and the message is changed when the icon is hovered over. For users that use the tab button however, the message isn't displayed.
The div with the message is an aria-live region, but since it is hidden, the screen reader will not announce the new message. Is there a way to announce the message despite the region being hidden?
The short answer is no. an aria-live region must be visible if you want its content changes to be announced.
You may read this question where I give a small trick: show the element a few seconds, long enough to let the screen reader read the message, and then hide again.
However you must show the message at least for 3-5 seconds because some screen readers cut of before the end if you hide the element while it is still being spoken.
IF showing the message for that long is unacceptable, you can still put it off-screen, by using a little CSS like below.
Note that many frameworks already have classes like .visually-hidden, .sr-only, etc. with a similar code. If you are using one of them, use what they define.
.visually-hidden {
top:0;
left:-2px;
width:1px;
height:1px;
position:absolute;
overflow:hidden;
}
```
I've found a better way to handle this issue. First you'll have to always set your live element visibile in DOM, not UI, and after that, you'll need to update the textContent, or innerHTML; next step, just setTimeout for a couple of milliseconds (100ms in my case) or more, and after that just clear the contents inside. In this way the screen reader will still read the previous message entirely. You can also clearTimeout if new messages has to be appended in the meantime.
example:
function updateLiveRegion(message) {
const elem = document.getElementById("my-id");
elem.textContent = message;
clearTimeout(myTimeout);
myTimeout = setTimeout(() => {
elem.textContent = "";
} , 1000);
}
updateLiveRegion("my message");
Related
In a web page I have an input field and a div that is fixed to the bottom of the window (with these CSS properties: position:fixed; and bottom:0;
I made a Codepen to show what I'm talking about: https://codepen.io/anon/pen/xpQWbb/
Chrome on Android keeps the div visible even when the soft keyboard is open:
However, Safari on iOS seems to draw the soft keyboard over the fixed element:
(I should mention I'm testing on the iOS simulator on my Macbook, because I don't have a working iPhone)
Is there a way to make iOS Safari keep the element visible even when the soft keyboard is open, like how Chrome does it?
I recently ran in to this problem when creating a chat input that should stay fixed at the bottom of the page. Naturally the iOS keyboard displayed on top of the chat input. Knowing the exact keyboard height seems more or less impossible. I embarked on a quest to find a solid value to base my calculations on so i can manually position the chat input container above the keyboard. I wanted to find the actual "innerHeight" value, in other words the currently visible area of the webpage. Due to how the iOS keyboard works, the only way to get that value with the keyboard open seems to be to scroll to the very bottom of the page, and then take a sample of "window.innerHeight".
So, i set up an event listener on my input field on 'click' (since on 'focus' caused a lot of issues for me). This opens the keyboard, which takes a while, so after i set a timeout for 1000ms to make sure (hopefully) that my keyboard is fully open. After 1000ms i quickly scroll to the bottom of the page with javascript, save the value of "window.innerHeight" in this state, and scroll back to where i was. This gives me the actual height of the visible area on the screen.
It seems like the browser window is placed behind the keyboard until you scroll to the very bottom, in which case the whole window 'scrolls up' and the bottom is placed at the top of the keyboard view.
Once i have this value i use currently scrolled value (window.scrollY) plus the value i saved minus the height of my absolute positioned element to determine where to place it. I opted to also hide the input while scrolling since it's flicking around quite a bit. Another downside to this is that you get a quick flick of the page when it does the measurement at the bottom.
Another thing i couldn't solve was the variable height of the address bar. I just made the input a bit higher than i needed so it would have some "padding" at the bottom.
var correctInnerHeight = window.innerHeight;
var isFocused = false;
var docHeight = $(document).height();
var input = $('.myInput');
input.click(function(e){
isFocused = true;
input.css('position', 'absolute');
// Wait for the keyboard to open
setTimeout(function(){
docHeight = $(document).height();
var lastScrollPos = $(document).scrollTop();
// Scroll to the bottom
window.scroll(0, $(document).height());
// Give it a millisecond to get there
setTimeout(function(){
// Save the innerHeight in this state
correctInnerHeight = window.innerHeight;
console.log(correctInnerHeight);
// Now scroll back to where you were, or wish to be.
window.scroll(0, lastScrollPos);
fixInputPosition();
// Make sure the input is focused
input.focus();
}, 1);
}, 1000);
});
input.on('blur', function(){
input.css('position', 'fixed');
input.css('top', 'auto');
input.css('bottom', '');
isFocused = false;
});
$(window).scroll(function(){
fixInputPosition();
});
function fixInputPosition(){
if(isFocused){
var offsetTop = ($(window).scrollTop() + correctInnerHeight) - input.height();
offsetTop = Math.min(docHeight, offsetTop);
input.css('top', offsetTop);
input.css('bottom', 'auto');
}
};
body, html{
margin: 0;
}
html{
width: 100%;
height: 2000px;
}
.myInput{
position: fixed;
height: 30px;
bottom: 0;
width: 100%;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type='text' class='myInput'>
Check out this thread, it talks about a work around that may be more feasible in terms of code. In brief it talks about using the height of the keyboard to move the content into view. All be it a bit hacky it may be difficult to pin down the exact height of the keyboard across devices.
Unfortunately, due to the nature of the IOs Safari keyboard it's not part of the browser viewport so cannot be referenced as you would do typical elements.
#Bhimbim's answer may a good shot too.
Regards,
-B
i experienced this before. What i did back then was :
Make a listener when keyboard is hit.
When keyboard is hit resize you webview's height with screen height - keyboard height.
To do this trick you need to make sure that you html is responsive.
I can show more code in the IOS side, if you're interested i can edit my answer and show you my IOS code. Thank you.
Hi again, sorry, i was mistaken, i thought you were creating apps with webview inside. If you still wanna do this by listening the keyboard i still have work around for you. It may not the perfect way, but i believe this will work if you want to try. Here my suggestion :
You still can have listener from webpage when the keyboard is up. You can put a listener on your textfield by jquery onkeyup or onfocus.
Then you will know when the input is hit and the keyboard will show.
Then you can create a condition in your java script to manipulate your screen.
Hope this give you an insight friend.
#Beaniie thank you !.
Hi Andreyu !. Yes correct, we can not know the keyboard height, not like my case with WebView, I can know the keyboard height through IOS code. I have another work around, not so smart, but might work. You can get the screen size and compare to array of IOS device screen size. Then you might narrowed down the keyboard height by surveying through IOS devices. Good luck friend.
Try using position:absolute and height:100% for the whole page.
When the system displays the keyboard,it plTaces it on top of the app content.
One way is to manage both the keyboard and objects is to embed them inside a UIScrollView object or one of its subclasses, like UITableView. Note that UITableViewController automatically resizes and repositions its table view when there is inline editing of text fields.
When the keyboard is displayed, all you have to do is reset the content area of the scroll view and scroll the desired text object into position. Thus, in response to a UIKeyboardDidShowNotification, your handler method would do the following:
1.Get the size of the keyboard.
2.Adjust the bottom content inset of your scroll view by the keyboard height.
3.Scroll the target text field into view.
Check the Apple developer's guideline to learn more:https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
is there a way to don't read 'unavailable' or 'dimmed' in a self-disable-button?
See the example Example
var saveBtn = document.getElementById("saveBtn");
var helper = document.getElementById("helper");
var content = document.getElementById("content");
saveBtn.onclick = function(e) {
saveBtn.setAttribute("disabled", "disabled");
saveBtn.setAttribute("aria-disabled", true);
content.innerHTML = 'Lorem input a lot of stuffs';
helper.innerHTML = "Content added, please read it";
setTimeout(function(){
helper.innerHTML = "";
saveBtn.removeAttribute("disabled");
saveBtn.setAttribute("aria-disabled", false);
}, 5000);
};
Voice only says: 'Content added, please read it'
NVDA says: 'Content added, please read it. Unavailable'
I know that it is happening because the button still having focus. But, I need to find a solution for that because I can't modify the current behavior of my page.
I'm using the html helper to inform transitions as you can see https://stackoverflow.com/a/38137593/3438124.
Sorry for ugly code, this is only to simulate my real behavior.
Thank you guys!
You will need to move focus to the new element. Give it tabindex=0 and then use focus().
aria-live is for when you want to have the screen reader speak changes for the content area with the aria-live attribute. Since you use it on a sibling div, you are missing the opportunity for it to just do what it does. Alternative, you can skip the tabindex/focus() approach and just put aria-live on a container for your new content.
Also, you can ditch the aria-disabled and just lean on disabled for the button. aria-disabled is for elements that otherwise do not support disabled.
You answered your own question when you said "I know that it is happening because the button still having focus" and then aardrian pointed it out explicitly when he said "You will need to move focus to the new element".
Keep in mind, though, that whether the focus remains on a disabled object is totally up to the user agent (browser). Some browsers leave the focus on the disabled object and others move it to the parent.
aardrian's comment about using tabindex was if you wanted to move the focus to an object that is not normally focusable (ie, if it's an element you can't normally TAB to). So if you want to move the focus to some simple text (just to get it off the button), then I'd tweak aardrian's suggestion and use tabindex='-1' instead of tabindex='0'. That will allow you to call focus() on that element but won't allow the user to TAB to it.
If you want to move the focus to another button on the page, or some other element that can naturally receive focus (checkbox, input field, etc), then you don't need tabindex.
If you end up moving the focus to the text that was just added, then you don't really need aria-live because the screen reader will read the text that you just focused to. But that's only relative to the example you posted, which I understand is just a sample to show the problem. Your real app might not be adding text.
And I want to second aardrian's recommendation of not setting aria-disabled when you're already using the disabled property. It's superfluous. The aria-disabled property is for when you're simulating a disabled object.
How the text is read varies reader to reader. None is right or wrong. If you want the text being read out to be same and consistent across the readers..Do a little workaround. Add an aria-label="desired text" to your button. This will override anything that is present inside the button tag.
eg
<button aria-label="desired text button">This text will be ignored</button>
Nvda will read the button as "desired text button". Text inside button is ignored. Now you can handle(add/remove) the disabled and aria-label attribute with JS.
In the context of your question you can try:
Instead of:
helper.innerHTML = "Content added, please read it";
Try using:
saveBtn.setAttribute("aria-label", "Content added, please read it.");
setTimeout(function(){
saveBtn.removeAttribute("aria-label");}, 5000);
Then the text will be same across the readers.
I have a page that has 2 steps to register a user.
After a user has filled out all fields of the first section, he needs to confirm the "Terms and Conditions" and press a button to confirm it.
After he has pressed the button, first section is becomes readOnly and the second section (more fields to fill) appears at the bottom of the page and the page does a scrollTo this new section.
I need to inform the screen reader that there is a new section on the same page but I don't know who can I do it.
I appreciate your help!
In your html have an empty span/div with aria-live="assertive". In your button click function, add the text you want the reader to announce to that span.
(This is the same function where you will be taking focus to that section.)
Don't forget to empty it outside the function to make it announce properly next time also.
Aria-assertive text will be announced each time it is changed.
Eg.
In HTML
<span id="announce" aria-live="assertive"></span>
<button id="btn">Click</button>
In javascript
$("#btn").click(function(){
$("#announce").text("Scrolled to a new section");
});
This is about focus management. You need somewhere to anchor focus that makes sense to the user and you need to then move that focus.
This is not an ideal solution overall, but lacking context for the larger objective I'll give you the bits to get this part functional / useful.
First, add this style so you can see where the focus is going (you can remove/change it later):
*:focus {
outline: 2px solid #f00;
}
Now as you tab through the page you can see where the focus lives.
Then in your script where you create the new form (I recommend you actually just hide this section and display it instead of writing it in via JS, but I understand this may be a demo), update the <h3> to be focusable by adding an id attribute and then a tabindex so that you can drop focus on it. I use the <h3> you already have since it provides the context for the user and in this case overrides my general distaste for using tabindex on non-interactive elements.
<h3 id="second" tabindex="0">
Then add bit of script after your setTimeout() that moves the focus to that element (thereby ensuring it has been rendered and can receive focus):
var secondForm = document.getElementById('second');
secondForm.focus();
Now when you click the "Continue!" button, the page scrolls and the heading will receive focus, get announced (you can add instruction text, etc), and the user may continue.
You will probably need to massage the script a bit, maybe stuffing it in its own timer function to be certain it only fires when you want, but overall the general concept is in there.
I made a pen to demo it.
I have a map, which has about 3-5 divs on it. When someone clicks a div, it flips and a text appears on the back of the div. But if someone has the curser on a div and scrolls, the map should behave like it always does and zoom.
I found this in another question: http://jsfiddle.net/ZqfTX/. Is there a way to say
dblclick: function(){$(this).[IGNORE]} ?
My idea is only an idea. If you have another way to do it, even better.
This should take care of what you want. Here is an updated jsFiddle which sets a timeout and prevents a double click from occurring. You can set whatever amount of time you want before a second single click is allowed. It also detects the scroll of the mouse wheel and alerts up or down based on the direction of the scroll.
From your OP and comments, I believe this should accomplish everything you were asking. As far as the animation of your div, I don't know what you are doing with it so that part would need to be changed to do what you want.
The code snippet below was found and modified from this other SO post and many thanks to that poster: How to disable double clicks or timeout clicks with jQuery?
Preventing Double Click
jQuery('.flip').click(function() {
var $this = jQuery(this);
if ($this.data('activated')) return false; // Pending, return
$this.data('activated', true);
setTimeout(function() {
$this.data('activated', false)
}, 1500); // Time to wait until next click can occur
doFlip(); // Call whatever function you want
return false;
});
function doFlip() {
if ($('.flip').find('.card').hasClass('flipped')) {
$('.flip').find('.card').removeClass('flipped');
} else {
$('.flip').find('.card').addClass('flipped');
}
}
On a side note, if you do not want the user to have the ability to even single click the div a second time, take a look at jQuery's .one() event handler attachment as it only allows execution once per element. Not sure if you want to have the ability to click it a second time but figured I'd throw it out there just in case.
If you want to prevent double click, you can try to use event.preventDefault
$(".yourClass").dblclick(function (e) {
e.preventDefault();
});
Looks like you'll have to bind event handlers for the events you want to pass through to the map and perform the zooming actions manually. This might help:
https://code.google.com/p/jquery-ui-map/
I've asked a few other questions here about this system, so I'll try to avoid repeating a lot of detail.
The short version is that I have many html pages, each with a form that accepts input, but never saves the input anywhere- they are only ever printed out for mailing. A previously developer who had never heard of #media print did the initial work on most of them, and so he came up with some... odd solutions to hide the ugly text boxes on the printed page, usually resulting in two completely separate copies of nearly the same html. Unfortunately, that broke the back button in many cases, and so now I must go back and fix them.
In some cases, these html forms really are form letters, with text inputs in the middle of the text. I can style the text inputs so that the box doesn't show, but they are still the wrong size. This results in a bunch of extra ugly whitespace where it doesn't belong. How can make the inputs fit the text entered by the user?
The best I can come up with at the moment is to have a hidden <span> next to each input that is styled to show instead of the input when printing, and use javascript to keep it in sync. But this is ugly. I'm looking for something better.
Update:
Most of our users are still in IE6, but we have some IE7 and firefox out there.
Update2:
I re-thought this a little to use a label rather than a span. I'll maintain the relationship using the label's for attribute. See this question for my final code.
The span idea isn't that bad. With a Javascript library like jquery, a single 1-2 line Javascript function could dynamically replace all the appropriate <input> tags with <span>..</span>. You wouldn't have to enter in any of the spans yourself.
In really rough pseudo-javascript code with jquery it would be something like:
function replaceInputsOnSomeButtonClick() {
// Find all inputs, wrap value with span tag, remove the input tag
$("input").text().wrap("<span>").remove();
}
CSS doesn't have answer for this one. Inputs are "replaced elements" – a black box in CSS.
input {content:attr(value)} works only for initial value in HTML, and won't reflect any later changes.
So the best you can get is less ugly Javascript.
for(var i=0; i < form.elements.length; i++)
{
var input = form.elements[i];
if (input.type != 'text') continue;
var span = document.createElement('span');
input.parentNode.insertBefore(span,input);
span.className = 'show-in-print';
input.className = 'hide-in-print';
input.onchange = (function(span){ // trick to preserve current value of span
return function()
{
if (span.firstChild) span.removeChild(span.firstChild);
span.appendChild(document.createTextNode(this.value));
}
})(span);
}