How to make an audio level indicator accessible? - html

I'm trying to build an accessible audio indicator for a WebRTC video chat. It should basically show how loud you're talking, when you're talking. Here is the isolated code (for Codesandbox, you need to install styled-components).
import React, { useEffect, useRef, useState } from "react";
import styled, { css } from "styled-components";
import "./styles.css";
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
const VolumeMeterWrapper = styled.div`
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.75);
display: flex;
height: 32px;
width: 32px;
`;
const VolumeBar = styled.div`
background: #25a968;
border-radius: 1px;
height: 4px;
margin: 0 2px;
width: 8px;
transition: all 0.2s linear;
transform: ${({ level }) => css`scaleY(${level + 1})`};
`;
function VolumeMeter({ level }) {
return (
<VolumeMeterWrapper>
<VolumeBar level={level > 3 ? ((level - 2) * 5) / 4 : 0} />
<VolumeBar level={level} />
<VolumeBar level={level > 3 ? ((level - 2) * 5) / 4 : 0} />
</VolumeMeterWrapper>
);
}
export default function App() {
const [level, setLevel] = useState(0);
const [count, setCount] = useState(0);
useInterval(() => {
setCount((c) => c + 1);
if (count % 10 > 4) {
setLevel((l) => l - 1);
} else {
setLevel((l) => l + 1);
}
}, 200);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
Level: {level}
<VolumeMeter level={level} />
</div>
);
}
In this example there is no real audio input as you can see. The "level" fakes how loud the person would be talking.
How would you make something like this accessible? Would you even need to do that (because inspecting the UI of various providers didn't show any special tags or aria-labels)?

Preword
This was an interesting one!
Firstly a small apology, the example is a little messy as I have tried to leave in a few parts of my process to give you options, if anything doesn't make sense then let me know!
It should be easy to tidy up and turn into a reusable component.
Answer
The actual part for announcing is simple, you just need a visually hidden paragraph on the page with aria-live on it and update the text inside it.
<p class="visually-hidden" aria-live="assertive">
//update the text in here and it will be announced.
</p>
What is more difficult is making a nice screen reader interface and announcer experience.
How I handled announcement
What I ended up doing was taking the average and peak volume over time and announcing a message based on those two parameters.
If the volume goes above 95 (an arbitrary number assuming volume goes to 100) or is consistently above 80 then I announce that the microphone is too loud.
If the volume is below 40 on average then I announce the microphone is too quiet.
If the average volume is below 10 then I assume the microphone is not working or they are not talking.
Otherwise I announce that the volume is ok.
The numbers will need some tweaking as I obviously simulated fluctuating volume levels and real world numbers may be different.
The other thing I did was make sure that the aria-live region only updated every 2 seconds. However I have a slow announcer speed on my screen reader so you could probably get away with about 1200ms.
This is to avoid flooding the announcer queue.
Alternatively you could set this to a much larger value (say 10 seconds) as it would then be useful for monitoring on an ongoing basis throughout a call. If you decide to do this then set the announcer to aria-live="polite" so it doesn't interrupt other screen reader announcements.
Further improvements I thought of
I didn't implement this but there are two things I can think of to make this more accurate (and less annoying, at the moment if would not be usable throughout a whole call) if you want it to be used as an ongoing monitoring tool throughout a call.
Firstly I would discard any values less than 10 for volume and only take an average of volumes above 10.
This would be more indicative of the actual volume level when the user is actively speaking.
Secondly I would discard all volume level announcements if someone else is talking. You would expect the user to be quiet at that point so no point telling them their microphone is quiet! Plus you don't really want announcements while others are talking.
The other way I tried
I did try announcing the volume as an integer value every 500ms, but as this was a snapshot I felt it was not very accurate as to what was happening. That is why I went for average volume.
I then realised that you could get an average of 50 but peak at 100 (clipping) so added peak volume to the checks as well.
Other things
1. announce on focus
I made it so that when you focus the EQ graph level thing (no idea what to call it! hehe) it announced while it had focus at first.
I realised this was better as a toggle button so that users can switch it on and off at will.
having a toggle is preferable as it allows you to fiddle with volume levels while listening to the announcements, it also allows everyone else to switch it on and off as per their preference
2. Announcing via an average and using phrases
The other advantage to taking the average and then announcing a phrase rather than a number is user experience if the indicator is left on.
It only announces when there is a change to the aria-live region, so as long as the volume stays in acceptable levels it won't announce anything. Obviously if they stop speaking it will announce "the microphone is too quiet", but yet again it will only announce this once.
3. prefers-reduced-motion
Users with vestibular disorders (sensitivity to movement disorders) may be distracted by the bars.
As such I have hidden the EQ altogether for those users.
Now because I have the EQ switched off by default this is not necessary. However if you decide to have the EQ switched on by default you can use the prefers-reduced-motion media query to set a much slower animation speed etc. That is why I have left it in, as an example of how to use it (it was an overhang from when I made the EQ work on focus rather than a toggle so isn't needed anymore).
I decided that the most accessible way to implement this was with the EQ switched off by default. This also helps people with attention disorders who may get easily distracted by the bars as well as making the page safe for people with epilepsy / seizure disorders as the bars could "flash" more than 3 times in a second.
4. Changing the button text.
I take advantage of our visually-hidden class when the EQ is switched on.
I change the text in the EQ and then visually hide it so that screen reader users still get useful button text when they focus the EQ but everyone else just gets to see the EQ bars.
Example
Ignore the first part of the JS that is just my shoddy way to make the fake EQ.
I have added a very large comment block to mark the start of the actual parts you need. It starts around line 65 in the JS.
There are parts left in for your reference for alternative ways to do things (announce on focus and prefers-reduced-motion in the CSS) so it is a little messy.
Any questions just ask.
//just making the random bars work, ignore this except for globalVars.currentVolume which is defined in the next section as that is what we use to announce the volume.
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function removeClass(selector, className){
var el = document.querySelectorAll(selector);
for (var i = 0; i < el.length; i++) {
el[i].classList.remove(className);
}
}
function addClass(selector, className){
var el = document.querySelectorAll(selector);
for (var i = 0; i < el.length; i++) {
el[i].classList.add(className);
}
}
var generateRandomVolume = function(){
var stepHeight = 0.8;
globalVars.currentVolume = randomInt(0,100);
setBars(globalVars.currentVolume * stepHeight);
setTimeout(generateRandomVolume, randomInt(102,250));
return;
}
function setBars(volume){
var bar1 = document.querySelector('.bar1');
var bar2 = document.querySelector('.bar2');
var bar3 = document.querySelector('.bar3');
var smallHeightProportion = 0.75;
var smallHeight = volume * smallHeightProportion;
bar1.style.height = smallHeight + "px";
bar2.style.height = volume + "px";
bar3.style.height = smallHeight + "px";
//console.log(globalVars.currentVolume);
if(globalVars.currentVolume < 80){
addClass('.bar', 'green');
removeClass('.bar', 'orange');
removeClass('.bar', 'red');
}else if(globalVars.currentVolume >= 90){
addClass('.bar', 'red');
removeClass('.bar', 'orange');
removeClass('.bar', 'green');
}else{
addClass('.bar', 'orange');
removeClass('.bar', 'red');
removeClass('.bar', 'green');
}
}
window.addEventListener('load', function() {
setTimeout(generateRandomVolume, 250);
});
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////// ACTUAL ANSWER ///////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//actual code you need, sorry it is a bit messy but you should be able to clean it up (and make it "Reactified")
// global variables, obviously only the currentVolume needs to be global so it can be used by both the bars and the announcer
var globalVars = {};
globalVars.currentVolume = 0;
globalVars.volumes = [];
globalVars.averageVolume = 0;
globalVars.announcementsOn = false;
//globalVars.indicatorFocused = false;
var audioIndicator = document.getElementById('audio-indicator');
var liveRegion = document.querySelector('.audio-indicator-announce');
var buttonText = document.querySelector('.button-text');
var announceTimeout;
//adjust the speech interval, 2 seconds felt right for me but I have a slow announcer speed, I would imagine real screen reader users could handle about 1200ms update times easily.
var config = {};
config.speakInterval = 2000;
//push volume level every 100ms for use in getting averages
window.addEventListener('load', function() {
setInterval(sampleVolume, 100);
});
var sampleVolume = function(){
globalVars.volumes.push(globalVars.currentVolume);
}
audioIndicator.addEventListener('click', function(e) {
toggleActive();
});
audioIndicator.addEventListener('keyup',function(e){
if (e.keyCode === 13) {
toggleActive();
}
});
function toggleActive(){
if(!audioIndicator.classList.contains('on')) {
audioIndicator.classList.add('on');
announceTimeout = setTimeout(announceVolumeInfo, config.speakInterval);
liveRegion.innerHTML = "announcing your microphone volume is now on";
buttonText.classList.add('visually-hidden');
buttonText.innerHTML = 'Mic<span class="visually-hidden">rophone</span> Level indicator on (click to turn off)';
console.log("SPEAK:","announcing your microphone volume is on");
}else{
audioIndicator.classList.remove('on');
clearTimeout(announceTimeout);
liveRegion.innerHTML = "announcing your microphone volume is now off";
buttonText.classList.remove('visually-hidden');
buttonText.innerHTML = 'Mic<span class="visually-hidden">rophone</span> Level indicator off (click to turn on)';
console.log("SPEAK:","announcing your microphone volume is off");
}
}
//switch on the announcer - deprecated idea, instead used toggle switch
//audioIndicator.addEventListener('focus', (e) => {
// setTimeout(announceVolumeInfo, config.speakInterval);
//});
//we take the average over the speakInterval. We also take the peak so we can see if the users microphone is clipping.
function getVolumeInfo(){
var samples = globalVars.volumes.length;
var totalVol = 0;
var avgVol, peakVol = 0;
var sample;
for(var x = 0; x < samples; x++){
sample = globalVars.volumes[x]
totalVol += sample;
if(sample > peakVol){
peakVol = sample;
}
}
globalVars.volumes = [];
var volumes = {};
volumes.average = totalVol / samples;
volumes.peak = peakVol;
return volumes;
}
var announceVolumeInfo = function(){
var volumeInfo = getVolumeInfo();
updateLiveRegion (volumeInfo.average, volumeInfo.peak);
//part of deprecated idea of announcing only on focus
//if(document.activeElement == document.getElementById('audio-indicator')){
// setTimeout(announceVolumeInfo, config.speakInterval);
//}
if(audioIndicator.classList.contains('on')) {
announceTimeout = setTimeout(announceVolumeInfo, config.speakInterval);
}
}
//we announce using this function, if you just want to read the current volume this can be as simple as "liveRegion.innerHTML = globalVars.currentVolume"
var updateLiveRegion = function(avgVolume, peak){
var speak = "Your microphone is ";
//doing it this way has a few advantages detailed in the post.
if(peak > 95 || avgVolume > 80){
speak = speak + "too loud";
}else if(avgVolume < 40){
speak = speak + "too quiet";
}else if(avgVolume < 10){
speak = speak + "not working or you are not talking";
}else{
speak = speak + "at a good volume";
}
console.log("SPEAK:", speak);
console.log("average volume:", avgVolume, "peak volume:", peak);
liveRegion.innerHTML = speak;
//optionally you could just read out the current volume level and that would do away with the need for tracking averages etc..
//liveRegion.innerHTML = "Your microphone volums level is " + globalVars.currentVolume;
}
#audio-indicator{
width: 100px;
height: 100px;
position: relative;
background-color: #333;
}
/** make sure we have a focus indicator, if you decide to make the item interactive with the mouse then also add a different hover state. **/
#audio-indicator:focus{
outline: 2px solid #666;
outline-offset: 3px;
}
#audio-indicator:hover{
background-color: #666;
cursor: pointer;
}
/*************we simply hide the indicator if the user has indicated that they do not want to see animations**********/
#media (prefers-reduced-motion) {
#audio-indicator{
display: none;
}
}
/***********my visually hidden class for hiding content visually but still making it screen reader accessible, preferable to sr-only etc as futureproofed and better compatibility********/
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
#audio-indicator .bar{
display: none;
}
#audio-indicator.on .bar{
display: block;
}
.bar, .button-text{
position: absolute;
top: 50%;
left: 50%;
min-height: 2px;
width: 25%;
transition: all 0.1s linear;
}
.button-text{
width: 90px;
transform: translate(-50%,-50%);
text-align: center;
color: #fff;
}
.bar1{
transform: translate(-175%,-50%);
}
.bar2{
transform: translate(-50%,-50%);
}
.bar3{
transform: translate(75%,-50%);
}
.green{
background-color: green;
}
.orange{
background-color: orange;
}
.red{
background-color: red;
}
dummy link for focus
<div class="audio-indicator-announce visually-hidden" aria-live="assertive">
</div>
<div id="audio-indicator" tabindex="0">
<div class="button-text">Mic<span class="visually-hidden">rophone</span> Level indicator off (click to turn on)</div>
<div class="bar bar1"></div>
<div class="bar bar2"></div>
<div class="bar bar3"></div>
</div>
dummy link for focus
Final Thoughts
While the above would work the question is "is an EQ graph relevant / a good experience from a UX perspective?".
You would have to user test to find that out.
Using a method similar to Zoom might be preferable (with a minor tweak and an accessible interface).
Before (or during) a call allow a user to listen to a pre recorded sound first. This allows them to set their own volume level.
Then allow the user to speak and then play back their audio so they can check their microphone levels / their microphone is working.
Allow users to switch on "automatically adjust my microphone level" and use a similar method to the one I am using of averages while talking to adjust their volume automagically throughout a call.
You could also have a screen reader option that allows the system to announce if it is adjusting their volume automatically.
You could also have a visual indicator on the microphone icon / button that shows an up or down arrow if the volume is adjusted automatically if you want.
This is obviously just a thought and you might have a good use case of an EQ graph way of doing things, as I said it was just a thought!

Related

How to duplicate slider in HTML (esp8266)

I'm doing something With an ESP8266, and I need some help with the HTML part of it.
I want to duplicate 1 slider:
The code is :
client.println("<!DOCTYPE html><html>");
client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the on/off buttons
// Feel free to change the background-color and font-size attributes to fit your preferences
client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}");
client.println(".slider { width: 300px; }</style>");
client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");
// Web Page
//client.println("</head><body><h1>ESP32 with Servo</h1>");
client.println("<p>Start Temperature: <span id=\"servoPos\"></span></p>");
client.println("<input type=\"range\" min=\"20\" max=\"30\" class=\"slider\" id=\"servoSlider\" onchange=\"servo(this.value)\" value=\""+valueString+"\"/>");
client.println("<script>var slider = document.getElementById(\"servoSlider\");");
client.println("var servoP = document.getElementById(\"servoPos\"); servoP.innerHTML = slider.value;");
client.println("slider.oninput = function() { slider.value = this.value; servoP.innerHTML = this.value; }");
client.println("$.ajaxSetup({timeout:1000}); function servo(pos) { ");
client.println("$.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");
client.println("</body></html>");
//GET /?value=180& HTTP/1.1
if(header.indexOf("GET /?value=")>=0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
//Rotate the servo
//myservo.write(valueString.toInt());
// Serial.println(valueString);
}
}
}
// Clear the header variable
header = "";
This gives me a slider, but I want another one for controlling a different thing, under a different variable.
Since I don't really understand HTML, I have tried to change some variables and duplicate the code myself, but without success. The slider works, but I get a lot of errors.
Second (independent) slider
client.println("<p>Start Sensor2: <span id=\"servoPos2\"></span></p>");
client.println("<input type=\"range\" min=\"20\" max=\"30\" class=\"slider\" id=\"servoSlider2\" onchange=\"servo2(this.value)\" value=\""+valueString+"\"/>");
client.println("<script>var slider2 = document.getElementById(\"servoSlider2\");");
client.println("var servoP2 = document.getElementById(\"servoPos2\"); servoP2.innerHTML = slider2.value;");
client.println("slider2.oninput = function() { slider2.value = this.value; servoP2.innerHTML = this.value; }");
The function servo(pos) (my guess) in the rest code not shown has either to be
duplicated or ???
to stop guessing and helping you please add the following info:
Source code esp8266 or if it is a standard example the name of the ino file
The type of error- during compilation, on the browser console, on serial port?

Retina Devices in web developing: Do I still need to have 2x images?

A lot of the information about Retina devices comes from ~2013 but not much recently.
It seems like, for example in retina.js, it includes anything with a device pixel ratio of > 1.5 to be "retina", but don't all smartphones have well over 1.5 these days? My desktop computer does as well.
My question then, why not just always serve the highest possible resolution images you have access to instead of creating the half-sized versions for "non-retina" devices, which as far as I know don't really exist much and won't suffer much from being served a higher resolution image.
Thanks!!!
Using 2x images is a huge pain.
Who can know what is "best," but I'm currently working with images in a combo like this:
I use a parent element so that the image will fill it - and that parent/or it's ancestors will determine any limits. You can use the picture element. (usually, the src are supplied by a CMS or something {{image.small.url}} etc.
The official answer to your questions would be, that people don't serve the higher res file to everything - because the file is bigger and they want the site to load as fast as possible. / but if you double the images size (twice as big as it will ever be presented, and compress to ~40 or so) then use the parent element to size it - it's actually a smaller file size. There are very specific studies on this. I don't know how that works for painting and browser rendering, though.
MARKUP
<figure class='poster'>
<img src='' alt=''
data-small='http://placehold.it/600'
data-medium='http://placehold.it/1000'
data-large='http://placehold.it/2000'
/>
</figure>
STYLES (stylus)
figure // just imagine the brackets if you want
margin: 0
img
display: block
width: 100%
height: auto
.poster
max-width: 400px
SCRIPT
$(document).on('ready', function() {
// $global
var $window = $(window);
var windowWidth;
var windowHeight;
function getWindowDimentions() {
windowWidth = $window.width();
windowHeight = $window.height();
}
function setResponsibleImageSrc(imageAncestorElement, container) {
var large = false; // innocent until proven guilty
var medium = false; // "
var context;
if ( !container ) {
context = windowWidth;
} else {
context = $(container).outerWidth();
}
var large = context > 900;
var medium = context > 550;
$(imageAncestorElement).each( function() {
var $this = $(this).find('img');
var src = {};
src.small = $this.data('small');
src.medium = $this.data('medium');
src.large = $this.data('large');
if ( large ) {
$this.attr('src', src.large);
} else if ( medium ) {
$this.attr('src', src.medium);
} else {
$this.attr('src', src.small);
}
});
};
$window.on('resize', function() { // this should jog a bit
getWindowDimentions();
setResponsibleImageSrc('.poster', 'body');
}).trigger('resize');
});
It all depends on what you are doing - and there is no silver bullet yet. The context for each image is so unique. My goal is to get in the ballpark for each size - keep the images compressed to 40 in Photoshop on export... double the size they should be, and then the parent squishes them for retina. The size is actually smaller in most cases.
CodePen example: http://codepen.io/sheriffderek/pen/bqpPra

Reduce the size of text in angularjs when line breaks?

I have a responsive app for desktop and mobile.
In the app i have a div which randomly shows texts of all kinds of lengths.
I want to do the following:
If the line breaks because the length of the text is too wide for the width of that div, i want the font-size to reduce itself (I am using em's in my app).
Is it something i need to build directive for it? is it something that was built and used wildly?
Writing a robust solution for this problem is going to be non-trivial. As far as I know, there's no way to tell whether a line of text breaks. However, we do know the criteria for line breaking is the width of the text being wider than the element, accounting for padding.
The Canvas API has a method called measureText which can be used to measure a string, using a given context with a font and size set. If you spoof the settings of the element with a canvas, then you can measure the text with the canvas and adjust the size until it fits without overflowing.
I've written up a rough implementation of the way I would tackle this.
function TextScaler(element) {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
var scaler = {};
scaler.copyProps = function() {
var style = element.style.fontStyle,
family = element.style.fontFamily,
size = element.style.fontSize,
weight = element.style.fontWeight,
variant = element.style.fontVariant;
context.font = [style, variant, weight, size, family].join(' ');
};
scaler.measure = function(text) {
text = text || element.innerText;
return context.measureText(text);
};
scaler.overflows = function() {
var style = window.getComputedStyle(element),
paddingLeft = style['padding-left'],
paddingRight = style['padding-right'],
width = style.width - paddingLeft - paddingRight;
return scaler.measure() > width;
};
scaler.decrease = function() {
// decrease font size by however much
};
scaler.auto = function(retries) {
retries = retries || 10;
if(retries <= 0) {
scaler.apply();
console.log('used all retries');
}
if(scaler.overflows()) {
scaler.decrease();
scaler.auto(retries - 1);
} else {
console.log('text fits');
scaler.apply();
}
};
scaler.apply = function() {
// copy the properties from the context
// back to the element
};
return scaler;
}
After you've sorted out some of the blank details there, you'd be able to use the function something like this:
var element = document.getElementById('');
var scaler = TextScaler(element);
scaler.auto();
If it doesn't manage to decrease it within 10 retries, it will stop there. You could also do this manually.
while(scaler.overflows()) {
scaler.decrease();
}
scaler.apply();
You'd probably want some fairly fine tuned logic for handling the decrease function. It might be easiest to convert the ems to pixels, then work purely with integers.
This API could quite trivially be wrapped up as a directive, if you want to use this with Angular. I'd probably tackle this with two attribute directives.
<div text-scale retries="10">Hello world</div>
Of course, if it's not important that all the text is there onscreen, then you can just use the text-overflow: ellipsis CSS property.

CSS - flexible number of columns with width determined by content - possible?

This HTML fragment spreads a long list of short items over a series of columns so that it's easier to scan and doesn't take up as much vertical space. Everything is neatly aligned and the number of columns automatically adjusts itself if you resize the browser window. That's all great. The only problem is that the width of each column is hardcoded in the CSS.
<!doctype html>
<style>
ul.hlist {
width: 80%;
column-width: 6em;
-moz-column-width: 6em;
-webkit-column-width: 6em;
}
ul.hlist > li { display: block; }
</style>
<p>This header unconditionally uses one or more of these deprecated
integer typedefs:</p>
<ul class="hlist">
<li><code>caddr_t</code></li>
<li><code>daddr_t</code></li>
<li><code>fsid_t</code></li>
<li><code>quad_t</code></li>
<li><code>u_int_t</code></li>
<li><code>u_quad_t</code></li>
<li><code>u_int16_t</code></li>
<li><code>u_int32_t</code></li>
<li><code>u_int64_t</code></li>
<li><code>u_int8_t</code></li>
<li><code>u_char</code></li>
<li><code>u_short</code></li>
<li><code>u_int</code></li>
<li><code>u_long</code></li>
<li><code>n_short</code></li>
<li><code>n_long</code></li>
<li><code>n_time</code></li>
</ul>
With some JavaScript, one can scan the list and override column-width to the actual width of the widest list item; there's some fiddliness because each <li> is stretched to the current width of the column, so you have to look inside, but that's not a serious hurdle.
function innerWidth (el) {
var kids = el.children;
var w = 0;
for (var i = 0; i < kids.length; i++)
w += kids[i].offsetWidth;
return w;
}
function setColumnWidth (list) {
var items = list.children;
var mW = 0;
for (var i = 0; i < items.length; i++) {
var w = innerWidth(items[i]);
if (w > mW)
mW = w;
}
list.setAttribute("style",
"column-width:" + mW + "px;" +
"-moz-column-width:" + mW + "px;" +
"-webkit-column-width:" + mW + "px");
}
document.addEventListener("DOMContentLoaded", function (e) {
var lists = document.getElementsByClassName("hlist");
for (var i = 0; i < lists.length; i++)
setColumnWidth(lists[i]);
});
The question is, is there any way to get the same (or nearly so) effect as this JS using only CSS? Please note:
In the example, every <li> has its contents wrapped in a <code>, but your answer must not rely on this, because it ain't necessarily so in other cases.
I already have an entirely client-side solution, and additional server-side processing in context is extremely awkward, so I strongly prefer client-only answers.
Answers involving experimental or not-yet-deployed-at-all CSS features are just fine.
Rendering without JS: http://jsfiddle.net/7F8n6/2/ (columns are rather too wide)
Rendering with JS: http://jsfiddle.net/7F8n6/3/
The items are inside <code> elements so by default are rendered with a monospaced font. That means their width only depends on the number of characters.
Assuming you can influence the complete page on the server, determine the item with the most characters on the server. Here it's 9.
Now generate a CSS rule in the page that sets the column width to 9 characters:
column-width: 9ch;
-moz-column-width: 9ch;
-webkit-column-width: 9ch;
The 'ch' unit is the width of a single '0' (zero). It's not supported on every browser, see MDN, so I don't know if this solution is good enough for you.

flash vertical menu - adding code for scrollpane

I've got a flash vertical menu which has auto-scroll but it scrolls too fast and the only way to slow it down is to increase the height of the button.
I would like to replace the code with a scrollpane so I can control the menu:
Code chunk I need to remove then add the additional scrollpane code
menu_mc_Europe.onEnterFrame = function() {
if (_ymouse>10 && _ymouse<boundry_mc._height && _xmouse>0 && _xmouse<boundry_mc._width) {
ratio = (menu_mc_Europe._height-boundry_mc._height)/(boundry_mc._height);
if (_ymouse>boundry_mc._height/2) {
destScroll = -(_ymouse)*ratio-menu_mc_Europe.bttn_mc._height*ratio;
} else {
destScroll = -(_ymouse)*ratio
}
menu_mc_Europe._y += Math.round((destScroll-menu_mc_Europe._y)/5);
if (menu_mc_Europe._y<-(totalBttns*menu_mc_Europe.bttn_mc._height-boundry_mc._height)) {
menu_mc_Europe._y = -(totalBttns*menu_mc_Europe.bttn_mc._height-boundry_mc._height);
}
} else {
destScroll = 1;
menu_mc_Europe._y += Math.round((destScroll-menu_mc_Europe._y)/5);
}
};
hard to read that code w/out line breaks - what does 'ratio' represent above? is it a variable you can set? if not, just a wild guess, but try changing the divisor (the 5) in the '_y)/5' sections above?