HTML5 number field arrows customization - html

Can I customize
<input type='number'>
field to show all the time it's arrows? By default it's hidden till the field is has no focus. Below is what I'm talking about.

Firefox and IE don't have such behavior. So, I assume you are working with Google Chrome.
input::-webkit-inner-spin-button {
opacity: 1;
}
FYI. UA stylesheet has the following:
input::-webkit-inner-spin-button {
...
opacity: 0;
pointer-events: none;
}
input:enabled:read-write:-webkit-any(:focus,:hover)::-webkit-inner-spin-button {
opacity: 1;
pointer-events: auto;
}
html.css

The UI and behavior of <input type='number'>, as well as all the other HTML5 input types (e.g., type='date', etc), is browser and/or system dependent. To make the arrows always visible, you'd need to use a custom JS solution.

Only way that I can think of is... Having two buttons for incrementing and decrementing your input and using JS. You won't be using type="number" here since the JS will be incrementing and decrementing the number for you.
Here is an example, as mentioned here:
CSS:
.spin {
display: inline-block;
}
.spin span {
display: inline-block;
width: 20px;
height: 22px;
text-align: center;
padding-top: 2px;
background: #fff;
border: 1px solid #aaa;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.spin span:first-child {
border-radius: 4px 0 0 4px;
}
.spin input {
width: 40px;
height: 20px;
text-align: center;
font-weight: bold;
}
JS:
var spins = document.getElementsByClassName("spin");
for (var i = 0, len = spins.length; i < len; i++) {
var spin = spins[i],
span = spin.getElementsByTagName("span"),
input = spin.getElementsByTagName("input")[0];
input.onchange = function() { input.value = +input.value || 0; };
span[0].onclick = function() { input.value = Math.max(0, input.value - 1); };
span[1].onclick = function() { input.value -= -1; };
}
Note: Change background: #fff; to change the arrow colors. There are other neat examples available on the web as well!
Demo

Related

How to create a box around around controls in webprgramming

I have a few controls that I am attempting to encapsulate on my webpage. I have tried a few different methods on encapsulating my controls and they have not succeeded. I tried using a div and this did not work too well and I have also tried this post:
Create a group box around certain controls on a web form using CSS
What is happening is that a box is being created but it is at the top of my webpage instead of around the controls.
I would like to create a grey box similar to the ones found on this webpage:
https://img.labnol.org/di/trigger1.png
Below, I am attaching a copy of the CSS and HTML code that I am using in order to create my form. The form is a simple file upload form that I tweaked from an example. I am using this on my own, personal website.
Here is the HTML:
<!DOCTYPE html>
<html>
<head>
<script>
/* Script written by Adam Khoury # DevelopPHP.com */
/* Video Tutorial: http://www.youtube.com/watch?v=EraNFJiY0Eg */
function _(el){
return document.getElementById(el);
}
function uploadFile(){
var file = _("file1").files[0];
// alert(file.name+" | "+file.size+" | "+file.type);
var formdata = new FormData();
formdata.append("file1", file);
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", progressHandler, false);
ajax.addEventListener("load", completeHandler, false);
ajax.addEventListener("error", errorHandler, false);
ajax.addEventListener("abort", abortHandler, false);
ajax.open("POST", "file_upload_parser.php");
ajax.send(formdata);
}
function progressHandler(event){
//_("loaded_n_total").innerHTML = "Uploaded "+event.loaded+" bytes of "+event.total;
var percent = (event.loaded / event.total) * 100;
_("progressBar").value = Math.round(percent);
_("status").innerHTML = Math.round(percent)+"% uploaded... please wait";
}
function completeHandler(event){
_("status").innerHTML = event.target.responseText;
_("progressBar").value = 0;
document.getElementById('p1').innerHTML = "Drag your file here or click in this area.";
}
function errorHandler(event){
_("status").innerHTML = "Upload Failed";
}
function abortHandler(event){
_("status").innerHTML = "Upload Aborted";
}
function changeText()
{
document.getElementById('p1').innerHTML = "1 file selected";
}
</script>
<link rel="stylesheet" href="test.css">
</head>
<body>
<h2>Upload</h2>
<fieldset>
<legend>Group 1</legend>
<form id="upload_form" enctype="multipart/form-data" method="post">
<input type="file" name="file1" id="file1"><br>
<p id="p1">Drag your file here or click in this area.</p>
<input type="button" value="Upload File" onclick="uploadFile()">
<progress id="progressBar" value="0" max="100" style="width:508px; margin-left: -4px; margin-top: 10px;"></progress>
<h3 id="status"></h3>
<p id="loaded_n_total"></p>
</form>
</fieldset>
<script>
// self executing function here
(function() {
document.getElementById('upload_form')[0].onchange = changeText;
})();
</script>
</body>
</html>
Here is the CSS (which is referred to in the html as test.css):
body{
background: rgba(0,0,0,0.0);
}
form{
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: -250px;
width: 500px;
height: 200px;
border: 4px dashed #0D0D0D;
}
form p{
width: 100%;
height: 100%;
text-align: center;
line-height: 140px;
color: #0D0D0D;
font-family: Arial;
}
h2{
text-align: center;
}
form input[type="file"]{
position: absolute;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
outline: none;
opacity: 0;
}
form input[type="button"]{
margin: 0;
color: #fff;
background: #16a085;
border: none;
width: 508px;
height: 35px;
margin-top: -20px;
margin-left: -4px;
border-radius: 4px;
border-bottom: 4px solid #117A60;
transition: all .2s ease;
outline: none;
}
form input[type="button"]:hover{
background: #149174;
color: #0C5645;
}
form input[type="button"]:active{
border:0;
}
form progressBar{
text-align: center;
}
Coming back to the HTML, the fieldset tags are placed around the controls that I am attempting to encapsulate. I left them there so that anyone can see the main issue that I am running into.
I apologize but I am very new to web programming. Any help will be greatly appreciated, thank you.
Note: how the box is created doesn't really matter to me. I would expect that the box is created in HTML and then I can style it using CSS.
The structure of your HTML is fine, but the position: absolute properties in your CSS are clashing with the fieldset.
Since <fieldset> is wrapping all your controls, I would suggeset giving it a fixed width and height and position your child elements based on that, i.e. use width: 100% for your children and give all of them the same margin so they align nicely. Also make sure you either edit your #progressBar style in the markup.
Here's a snippet with the changes I just mentioned:
body {
background: rgba(0, 0, 0, 0.0);
}
fieldset {
width: 508px;
height: 270px;
/* fixed width and height*/
margin: 13vh auto;
}
#p1 {
border: 4px dashed #0D0D0D;
/* modified the actual text box instead of the entire form */
width: 508px;
height: 140px;
line-height: 140px;
margin-top: 0px;
}
form p {
text-align: center;
color: #0D0D0D;
font-family: Arial;
}
h2 {
text-align: center;
}
form input[type="file"] {
position: absolute;
margin: 0;
outline: none;
width: 508px;
height: 140px;
margin: 22px 4px;
opacity: 1;
background-color: orange;
/* Last two properties are a visual representation. Delete background-color and set opacity to 0 */
}
form input[type="button"] {
margin: 0;
color: #fff;
background: #16a085;
border: none;
width: 100%;
/* width relative to parent fieldset */
height: 35px;
margin-top: -20px;
border-radius: 4px;
border-bottom: 4px solid #117A60;
transition: all .2s ease;
outline: none;
}
form input[type="button"]:hover {
background: #149174;
color: #0C5645;
}
form input[type="button"]:active {
border: 0;
}
form progressBar {
text-align: center;
}
<!DOCTYPE html>
<html>
<head>
<script>
/* Script written by Adam Khoury # DevelopPHP.com */
/* Video Tutorial: http://www.youtube.com/watch?v=EraNFJiY0Eg */
function _(el) {
return document.getElementById(el);
}
function uploadFile() {
var file = _("file1").files[0];
// alert(file.name+" | "+file.size+" | "+file.type);
var formdata = new FormData();
formdata.append("file1", file);
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", progressHandler, false);
ajax.addEventListener("load", completeHandler, false);
ajax.addEventListener("error", errorHandler, false);
ajax.addEventListener("abort", abortHandler, false);
ajax.open("POST", "file_upload_parser.php");
ajax.send(formdata);
}
function progressHandler(event) {
//_("loaded_n_total").innerHTML = "Uploaded "+event.loaded+" bytes of "+event.total;
var percent = (event.loaded / event.total) * 100;
_("progressBar").value = Math.round(percent);
_("status").innerHTML = Math.round(percent) + "% uploaded... please wait";
}
function completeHandler(event) {
_("status").innerHTML = event.target.responseText;
_("progressBar").value = 0;
document.getElementById('p1').innerHTML = "Drag your file here or click in this area.";
}
function errorHandler(event) {
_("status").innerHTML = "Upload Failed";
}
function abortHandler(event) {
_("status").innerHTML = "Upload Aborted";
}
function changeText() {
document.getElementById('p1').innerHTML = "1 file selected";
}
</script>
<link rel="stylesheet" href="test.css">
</head>
<body>
<h2>Upload</h2>
<fieldset>
<legend>Group 1</legend>
<form id="upload_form" enctype="multipart/form-data" method="post">
<input type="file" name="file1" id="file1"><br>
<p id="p1">Drag your file here or click in this area.</p>
<input type="button" value="Upload File" onclick="uploadFile()">
<!-- changed progressBar style -->
<progress id="progressBar" value="0" max="100" style="width:100%; margin-top: 10px;"></progress>
<h3 id="status"></h3>
<p id="loaded_n_total"></p>
</form>
</fieldset>
<script>
// self executing function here
(function() {
document.getElementById('upload_form')[0].onchange = changeText;
})();
</script>
</body>
</html>
Hope it helps!

How to Create Multiple Cursors in a single Range Slider? [duplicate]

Is it possible to make a HTML5 slider with two input values, for example to select a price range? If so, how can it be done?
I've been looking for a lightweight, dependency free dual slider for some time (it seemed crazy to import jQuery just for this) and there don't seem to be many out there. I ended up modifying #Wildhoney's code a bit and really like it.
function getVals(){
// Get slider values
var parent = this.parentNode;
var slides = parent.getElementsByTagName("input");
var slide1 = parseFloat( slides[0].value );
var slide2 = parseFloat( slides[1].value );
// Neither slider will clip the other, so make sure we determine which is larger
if( slide1 > slide2 ){ var tmp = slide2; slide2 = slide1; slide1 = tmp; }
var displayElement = parent.getElementsByClassName("rangeValues")[0];
displayElement.innerHTML = slide1 + " - " + slide2;
}
window.onload = function(){
// Initialize Sliders
var sliderSections = document.getElementsByClassName("range-slider");
for( var x = 0; x < sliderSections.length; x++ ){
var sliders = sliderSections[x].getElementsByTagName("input");
for( var y = 0; y < sliders.length; y++ ){
if( sliders[y].type ==="range" ){
sliders[y].oninput = getVals;
// Manually trigger event first time to display values
sliders[y].oninput();
}
}
}
}
section.range-slider {
position: relative;
width: 200px;
height: 35px;
text-align: center;
}
section.range-slider input {
pointer-events: none;
position: absolute;
overflow: hidden;
left: 0;
top: 15px;
width: 200px;
outline: none;
height: 18px;
margin: 0;
padding: 0;
}
section.range-slider input::-webkit-slider-thumb {
pointer-events: all;
position: relative;
z-index: 1;
outline: 0;
}
section.range-slider input::-moz-range-thumb {
pointer-events: all;
position: relative;
z-index: 10;
-moz-appearance: none;
width: 9px;
}
section.range-slider input::-moz-range-track {
position: relative;
z-index: -1;
background-color: rgba(0, 0, 0, 1);
border: 0;
}
section.range-slider input:last-of-type::-moz-range-track {
-moz-appearance: none;
background: none transparent;
border: 0;
}
section.range-slider input[type=range]::-moz-focus-outer {
border: 0;
}
<!-- This block can be reused as many times as needed -->
<section class="range-slider">
<span class="rangeValues"></span>
<input value="5" min="0" max="15" step="0.5" type="range">
<input value="10" min="0" max="15" step="0.5" type="range">
</section>
No, the HTML5 range input only accepts one input. I would recommend you to use something like the jQuery UI range slider for that task.
Coming late, but noUiSlider avoids having a jQuery-ui dependency, which the accepted answer does not. Its only "caveat" is IE support is for IE9 and newer, if legacy IE is a deal breaker for you.
It's also free, open source and can be used in commercial projects without restrictions.
Installation: Download noUiSlider, extract the CSS and JS file somewhere in your site file system, and then link to the CSS from head and to JS from body:
<!-- In <head> -->
<link href="nouislider.min.css" rel="stylesheet">
<!-- In <body> -->
<script src="nouislider.min.js"></script>
Example usage: Creates a slider which goes from 0 to 100, and starts set to 20-80.
HTML:
<div id="slider">
</div>
JS:
var slider = document.getElementById('slider');
noUiSlider.create(slider, {
start: [20, 80],
connect: true,
range: {
'min': 0,
'max': 100
}
});
Sure you can simply use two sliders overlaying each other and add a bit of javascript (actually not more than 5 lines) that the selectors are not exceeding the min/max values (like in #Garys) solution.
Attached you'll find a short snippet adapted from a current project including some CSS3 styling to show what you can do (webkit only). I also added some labels to display the selected values.
It uses JQuery but a vanillajs version is no magic though.
#Update: The code below was just a proof of concept. Due to many requests I've added a possible solution for Mozilla Firefox (without changing the original code). You may want to refractor the code below before using it.
(function() {
function addSeparator(nStr) {
nStr += '';
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + '.' + '$2');
}
return x1 + x2;
}
function rangeInputChangeEventHandler(e){
var rangeGroup = $(this).attr('name'),
minBtn = $(this).parent().children('.min'),
maxBtn = $(this).parent().children('.max'),
range_min = $(this).parent().children('.range_min'),
range_max = $(this).parent().children('.range_max'),
minVal = parseInt($(minBtn).val()),
maxVal = parseInt($(maxBtn).val()),
origin = $(this).context.className;
if(origin === 'min' && minVal > maxVal-5){
$(minBtn).val(maxVal-5);
}
var minVal = parseInt($(minBtn).val());
$(range_min).html(addSeparator(minVal*1000) + ' €');
if(origin === 'max' && maxVal-5 < minVal){
$(maxBtn).val(5+ minVal);
}
var maxVal = parseInt($(maxBtn).val());
$(range_max).html(addSeparator(maxVal*1000) + ' €');
}
$('input[type="range"]').on( 'input', rangeInputChangeEventHandler);
})();
body{
font-family: sans-serif;
font-size:14px;
}
input[type='range'] {
width: 210px;
height: 30px;
overflow: hidden;
cursor: pointer;
outline: none;
}
input[type='range'],
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
background: none;
}
input[type='range']::-webkit-slider-runnable-track {
width: 200px;
height: 1px;
background: #003D7C;
}
input[type='range']:nth-child(2)::-webkit-slider-runnable-track{
background: none;
}
input[type='range']::-webkit-slider-thumb {
position: relative;
height: 15px;
width: 15px;
margin-top: -7px;
background: #fff;
border: 1px solid #003D7C;
border-radius: 25px;
z-index: 1;
}
input[type='range']:nth-child(1)::-webkit-slider-thumb{
z-index: 2;
}
.rangeslider{
position: relative;
height: 60px;
width: 210px;
display: inline-block;
margin-top: -5px;
margin-left: 20px;
}
.rangeslider input{
position: absolute;
}
.rangeslider{
position: absolute;
}
.rangeslider span{
position: absolute;
margin-top: 30px;
left: 0;
}
.rangeslider .right{
position: relative;
float: right;
margin-right: -5px;
}
/* Proof of concept for Firefox */
#-moz-document url-prefix() {
.rangeslider::before{
content:'';
width:100%;
height:2px;
background: #003D7C;
display:block;
position: relative;
top:16px;
}
input[type='range']:nth-child(1){
position:absolute;
top:35px !important;
overflow:visible !important;
height:0;
}
input[type='range']:nth-child(2){
position:absolute;
top:35px !important;
overflow:visible !important;
height:0;
}
input[type='range']::-moz-range-thumb {
position: relative;
height: 15px;
width: 15px;
margin-top: -7px;
background: #fff;
border: 1px solid #003D7C;
border-radius: 25px;
z-index: 1;
}
input[type='range']:nth-child(1)::-moz-range-thumb {
transform: translateY(-20px);
}
input[type='range']:nth-child(2)::-moz-range-thumb {
transform: translateY(-20px);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div class="rangeslider">
<input class="min" name="range_1" type="range" min="1" max="100" value="10" />
<input class="max" name="range_1" type="range" min="1" max="100" value="90" />
<span class="range_min light left">10.000 €</span>
<span class="range_max light right">90.000 €</span>
</div>
Actually I used my script in html directly. But in javascript when you add oninput event listener for this event it gives the data automatically.You just need to assign the value as per your requirement.
[slider] {
width: 300px;
position: relative;
height: 5px;
margin: 45px 0 10px 0;
}
[slider] > div {
position: absolute;
left: 13px;
right: 15px;
height: 5px;
}
[slider] > div > [inverse-left] {
position: absolute;
left: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [inverse-right] {
position: absolute;
right: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [range] {
position: absolute;
left: 0;
height: 5px;
border-radius: 14px;
background-color: #d02128;
}
[slider] > div > [thumb] {
position: absolute;
top: -7px;
z-index: 2;
height: 20px;
width: 20px;
text-align: left;
margin-left: -11px;
cursor: pointer;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);
background-color: #FFF;
border-radius: 50%;
outline: none;
}
[slider] > input[type=range] {
position: absolute;
pointer-events: none;
-webkit-appearance: none;
z-index: 3;
height: 14px;
top: -2px;
width: 100%;
opacity: 0;
}
div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
border: transparent;
}
div[slider] > input[type=range]:focus {
outline: none;
}
div[slider] > input[type=range]::-webkit-slider-thumb {
pointer-events: all;
width: 28px;
height: 28px;
border-radius: 0px;
border: 0 none;
background: red;
-webkit-appearance: none;
}
div[slider] > input[type=range]::-ms-fill-lower {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-fill-upper {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-tooltip {
display: none;
}
[slider] > div > [sign] {
opacity: 0;
position: absolute;
margin-left: -11px;
top: -39px;
z-index:3;
background-color: #d02128;
color: #fff;
width: 28px;
height: 28px;
border-radius: 28px;
-webkit-border-radius: 28px;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;
}
[slider] > div > [sign]:after {
position: absolute;
content: '';
left: 0;
border-radius: 16px;
top: 19px;
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top-width: 16px;
border-top-style: solid;
border-top-color: #d02128;
}
[slider] > div > [sign] > span {
font-size: 12px;
font-weight: 700;
line-height: 28px;
}
[slider]:hover > div > [sign] {
opacity: 1;
}
<div slider id="slider-distance">
<div>
<div inverse-left style="width:70%;"></div>
<div inverse-right style="width:70%;"></div>
<div range style="left:0%;right:0%;"></div>
<span thumb style="left:0%;"></span>
<span thumb style="left:100%;"></span>
<div sign style="left:0%;">
<span id="value">0</span>
</div>
<div sign style="left:100%;">
<span id="value">100</span>
</div>
</div>
<input type="range" value="0" max="100" min="0" step="1" oninput="
this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[1].style.width=value+'%';
children[5].style.left=value+'%';
children[7].style.left=value+'%';children[11].style.left=value+'%';
children[11].childNodes[1].innerHTML=this.value;" />
<input type="range" value="100" max="100" min="0" step="1" oninput="
this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[3].style.width=(100-value)+'%';
children[5].style.right=(100-value)+'%';
children[9].style.left=value+'%';children[13].style.left=value+'%';
children[13].childNodes[1].innerHTML=this.value;" />
</div>
The question was: "Is it possible to make a HTML5 slider with two input values, for example to select a price range? If so, how can it be done?"
In 2020 it is possible to create a fully accessible, native, non-jquery HTML5 slider with two thumbs for price ranges. If found this posted after I already created this solution and I thought that it would be nice to share my implementation here.
This implementation has been tested on mobile Chrome and Firefox (Android) and Chrome and Firefox (Linux). I am not sure about other platforms, but it should be quite good. I would love to get your feedback and improve this solution.
This solution allows multiple instances on one page and it consists of just two inputs (each) with descriptive labels for screen readers. You can set the thumb size in the amount of grid labels. Also, you can use touch, keyboard and mouse to interact with the slider. The value is updated during adjustment, due to the 'on input' event listener.
My first approach was to overlay the sliders and clip them. However, that resulted in complex code with a lot of browser dependencies. Then I recreated the solution with two sliders that were 'inline'. This is the solution you will find below.
var thumbsize = 14;
function draw(slider,splitvalue) {
/* set function vars */
var min = slider.querySelector('.min');
var max = slider.querySelector('.max');
var lower = slider.querySelector('.lower');
var upper = slider.querySelector('.upper');
var legend = slider.querySelector('.legend');
var thumbsize = parseInt(slider.getAttribute('data-thumbsize'));
var rangewidth = parseInt(slider.getAttribute('data-rangewidth'));
var rangemin = parseInt(slider.getAttribute('data-rangemin'));
var rangemax = parseInt(slider.getAttribute('data-rangemax'));
/* set min and max attributes */
min.setAttribute('max',splitvalue);
max.setAttribute('min',splitvalue);
/* set css */
min.style.width = parseInt(thumbsize + ((splitvalue - rangemin)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
max.style.width = parseInt(thumbsize + ((rangemax - splitvalue)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
min.style.left = '0px';
max.style.left = parseInt(min.style.width)+'px';
min.style.top = lower.offsetHeight+'px';
max.style.top = lower.offsetHeight+'px';
legend.style.marginTop = min.offsetHeight+'px';
slider.style.height = (lower.offsetHeight + min.offsetHeight + legend.offsetHeight)+'px';
/* correct for 1 off at the end */
if(max.value>(rangemax - 1)) max.setAttribute('data-value',rangemax);
/* write value and labels */
max.value = max.getAttribute('data-value');
min.value = min.getAttribute('data-value');
lower.innerHTML = min.getAttribute('data-value');
upper.innerHTML = max.getAttribute('data-value');
}
function init(slider) {
/* set function vars */
var min = slider.querySelector('.min');
var max = slider.querySelector('.max');
var rangemin = parseInt(min.getAttribute('min'));
var rangemax = parseInt(max.getAttribute('max'));
var avgvalue = (rangemin + rangemax)/2;
var legendnum = slider.getAttribute('data-legendnum');
/* set data-values */
min.setAttribute('data-value',rangemin);
max.setAttribute('data-value',rangemax);
/* set data vars */
slider.setAttribute('data-rangemin',rangemin);
slider.setAttribute('data-rangemax',rangemax);
slider.setAttribute('data-thumbsize',thumbsize);
slider.setAttribute('data-rangewidth',slider.offsetWidth);
/* write labels */
var lower = document.createElement('span');
var upper = document.createElement('span');
lower.classList.add('lower','value');
upper.classList.add('upper','value');
lower.appendChild(document.createTextNode(rangemin));
upper.appendChild(document.createTextNode(rangemax));
slider.insertBefore(lower,min.previousElementSibling);
slider.insertBefore(upper,min.previousElementSibling);
/* write legend */
var legend = document.createElement('div');
legend.classList.add('legend');
var legendvalues = [];
for (var i = 0; i < legendnum; i++) {
legendvalues[i] = document.createElement('div');
var val = Math.round(rangemin+(i/(legendnum-1))*(rangemax - rangemin));
legendvalues[i].appendChild(document.createTextNode(val));
legend.appendChild(legendvalues[i]);
}
slider.appendChild(legend);
/* draw */
draw(slider,avgvalue);
/* events */
min.addEventListener("input", function() {update(min);});
max.addEventListener("input", function() {update(max);});
}
function update(el){
/* set function vars */
var slider = el.parentElement;
var min = slider.querySelector('#min');
var max = slider.querySelector('#max');
var minvalue = Math.floor(min.value);
var maxvalue = Math.floor(max.value);
/* set inactive values before draw */
min.setAttribute('data-value',minvalue);
max.setAttribute('data-value',maxvalue);
var avgvalue = (minvalue + maxvalue)/2;
/* draw */
draw(slider,avgvalue);
}
var sliders = document.querySelectorAll('.min-max-slider');
sliders.forEach( function(slider) {
init(slider);
});
* {padding: 0; margin: 0;}
body {padding: 40px;}
.min-max-slider {position: relative; width: 200px; text-align: center; margin-bottom: 50px;}
.min-max-slider > label {display: none;}
span.value {height: 1.7em; font-weight: bold; display: inline-block;}
span.value.lower::before {content: "€"; display: inline-block;}
span.value.upper::before {content: "- €"; display: inline-block; margin-left: 0.4em;}
.min-max-slider > .legend {display: flex; justify-content: space-between;}
.min-max-slider > .legend > * {font-size: small; opacity: 0.25;}
.min-max-slider > input {cursor: pointer; position: absolute;}
/* webkit specific styling */
.min-max-slider > input {
-webkit-appearance: none;
outline: none!important;
background: transparent;
background-image: linear-gradient(to bottom, transparent 0%, transparent 30%, silver 30%, silver 60%, transparent 60%, transparent 100%);
}
.min-max-slider > input::-webkit-slider-thumb {
-webkit-appearance: none; /* Override default look */
appearance: none;
width: 14px; /* Set a specific slider handle width */
height: 14px; /* Slider handle height */
background: #eee; /* Green background */
cursor: pointer; /* Cursor on hover */
border: 1px solid gray;
border-radius: 100%;
}
.min-max-slider > input::-webkit-slider-runnable-track {cursor: pointer;}
<div class="min-max-slider" data-legendnum="2">
<label for="min">Minimum price</label>
<input id="min" class="min" name="min" type="range" step="1" min="0" max="3000" />
<label for="max">Maximum price</label>
<input id="max" class="max" name="max" type="range" step="1" min="0" max="3000" />
</div>
Note that you should keep the step size to 1 to prevent the values to change due to redraws/redraw bugs.
View online at: https://codepen.io/joosts/pen/rNLdxvK
2022 - Accessible solution - 30 second solution to implement
This solution builds off of this answer by #JoostS. Accessibility is something none of the answers have focused on and that is a problem, so I built off of the above answer by making it more accessible & extensible since it had some flaws.
Usage is very simple:
Use the CDN or host the script locally: https://cdn.jsdelivr.net/gh/maxshuty/accessible-web-components/dist/simpleRange.min.js
Add this element to your template or HTML: <range-selector min-range="0" max-range="1000" />
Hook into it by listening for the range-changed event (or whatever event-name-to-emit-on-change you pass in)
That's it. View the full demo here. You can easily customize it by simply applying attributes like inputs-for-labels to use inputs instead of labels, slider-color to adjust the color, and so much more!
Here is a fiddle:
window.addEventListener('range-changed', (e) => {console.log(`Range changed for: ${e.detail.sliderId}. Min/Max range values are available in this object too`)})
<script src="https://cdn.jsdelivr.net/gh/maxshuty/accessible-web-components#latest/dist/simpleRange.min.js"></script>
<div>
<range-selector
id="rangeSelector1"
min-label="Minimum"
max-label="Maximum"
min-range="1000"
max-range="2022"
number-of-legend-items-to-show="6"
/>
</div>
<div>
<range-selector
id="rangeSelector1"
min-label="Minimum"
max-label="Maximum"
min-range="1"
max-range="500"
number-of-legend-items-to-show="3"
inputs-for-labels
/>
</div>
<div>
<range-selector
id="rangeSelector2"
min-label="Minimum"
max-label="Maximum"
min-range="1000"
max-range="2022"
number-of-legend-items-to-show="3"
slider-color="#6b5b95"
/>
</div>
<div>
<range-selector
id="rangeSelector3"
min-label="Minimum"
max-label="Maximum"
min-range="1000"
max-range="2022"
hide-label
hide-legend
/>
</div>
I decided to address the issues of the linked answer like the labels using display: none (bad for a11y), no visual focus on the slider, etc., and improve the code by cleaning up event listeners and making it much more dynamic and extensible.
I created this tiny library with many options to customize colors, event names, easily hook into it, make the accessible labels i18n capable and much more. Here it is in a fiddle if you want to play around.
You can easily customize the number of legend items it shows, hide or show the labels and legend, and customize the colors of everything, including the focus color like this.
Example using several of the props:
<range-selector
min-label="i18n Minimum Range"
max-label="i18n Maximum Range"
min-range="5"
max-range="555"
number-of-legend-items-to-show="6"
event-name-to-emit-on-change="my-custom-range-changed-event"
slider-color="orange"
circle-color="#f7cac9"
circle-border-color="#083535"
circle-focus-border-color="#3ec400"
/>
Then in your script:
window.addEventListener('my-custom-range-changed-event', (e) => { const data = e.detail; });
Finally if you see that this is missing something that you need I made it very easy to customize this library.
Simply copy this file and at the top you can see cssHelpers and constants objects that contain most of the variables you would likely want to further customize.
Since I built this with a Native Web Component I have taken advantage of disconnectedCallback and other hooks to clean up event listeners and set things up.
Here is a reusable double range slider implementation, base on tutorial Double Range Slider by Coding Artist
near native UI, Chrome/Firefox/Safari compatible
API EventTarget based, with change/input events, minGap/maxGap properties
let $ = (s, c = document) => c.querySelector(s);
let $$ = (s, c = document) => Array.prototype.slice.call(c.querySelectorAll(s));
class DoubleRangeSlider extends EventTarget {
#minGap = 0;
#maxGap = Number.MAX_SAFE_INTEGER;
#inputs;
style = {
trackColor: '#dadae5',
rangeColor: '#3264fe',
};
constructor(container){
super();
let inputs = $$('input[type="range"]', container);
if(inputs.length !== 2){
throw new RangeError('2 range inputs expected');
}
let [input1, input2] = inputs;
if(input1.min >= input1.max || input2.min >= input2.max){
throw new RangeError('range min should be less than max');
}
if(input1.max > input2.max || input1.min > input2.min){
throw new RangeError('input1\'s max/min should not be greater than input2\'s max/min');
}
this.#inputs = inputs;
let sliderTrack = $('.slider-track', container);
let lastValue1 = input1.value;
input1.addEventListener('input', (e) => {
let value1 = +input1.value;
let value2 = +input2.value;
let minGap = this.#minGap;
let maxGap = this.#maxGap;
let gap = value2 - value1;
let newValue1 = value1;
if(gap < minGap){
newValue1 = value2 - minGap;
}else if(gap > maxGap){
newValue1 = value2 - maxGap;
}
input1.value = newValue1;
if(input1.value !== lastValue1){
lastValue1 = input1.value;
passEvent(e);
fillColor();
}
});
let lastValue2 = input2.value;
input2.addEventListener('input', (e) => {
let value1 = +input1.value;
let value2 = +input2.value;
let minGap = this.#minGap;
let maxGap = this.#maxGap;
let gap = value2 - value1;
let newValue2 = value2;
if(gap < minGap){
newValue2 = value1 + minGap;
}else if(gap > maxGap){
newValue2 = value1 + maxGap;
}
input2.value = newValue2;
if(input2.value !== lastValue2){
lastValue2 = input2.value;
passEvent(e);
fillColor();
}
});
let passEvent = (e) => {
this.dispatchEvent(new e.constructor(e.type, e));
};
input1.addEventListener('change', passEvent);
input2.addEventListener('change', passEvent);
let fillColor = () => {
let overallMax = +input2.max;
let overallMin = +input1.min;
let overallRange = overallMax - overallMin;
let left1 = ((input1.value - overallMin) / overallRange * 100) + '%';
let left2 = ((input2.value - overallMin) / overallRange * 100) + '%';
let {trackColor, rangeColor} = this.style;
sliderTrack.style.background = `linear-gradient(to right, ${trackColor} ${left1}, ${rangeColor} ${left1}, ${rangeColor} ${left2}, ${trackColor} ${left2})`;
};
let init = () => {
let overallMax = +input2.max;
let overallMin = +input1.min;
let overallRange = overallMax - overallMin;
let range1 = input1.max - overallMin;
let range2 = overallMax - input2.min;
input1.style.left = '0px';
input1.style.width = (range1 / overallRange * 100) + '%';
input2.style.right = '0px';
input2.style.width = (range2 / overallRange * 100) + '%';
fillColor();
};
init();
}
get minGap(){
return this.#minGap;
}
set minGap(v){
this.#minGap = v;
}
get maxGap(){
return this.#maxGap;
}
set maxGap(v){
this.#maxGap = v;
}
get values(){
return this.#inputs.map((el) => el.value);
}
set values(values){
if(values.length !== 2 || !values.every(isFinite))
throw new RangeError();
let [input1, input2] = this.#inputs;
let [value1, value2] = values;
if(value1 > input1.max || value1 < input1.min)
throw new RangeError('invalid value for input1');
if(value2 > input2.max || value2 < input2.min)
throw new RangeError('invalid value for input2');
input1.value = value1;
input2.value = value2;
}
get inputs(){
return this.#inputs;
}
get overallMin(){
return this.#inputs[0].min;
}
get overallMax(){
return this.#inputs[1].max;
}
}
function main(){
let container = $('.slider-container');
let slider = new DoubleRangeSlider(container);
slider.minGap = 30;
slider.maxGap = 70;
let inputs = $$('input[name="a"]');
let outputs = $$('output[name="a"]');
outputs[0].value = inputs[0].value;
outputs[1].value = inputs[1].value;
slider.addEventListener('input', (e) => {
let values = slider.values;
outputs[0].value = values[0];
outputs[1].value = values[1];
});
slider.addEventListener('change', (e) => {
let values = slider.values;
console.log('change', values);
outputs[0].value = values[0];
outputs[1].value = values[1];
});
}
document.addEventListener('DOMContentLoaded', main);
.slider-container {
display: inline-block;
position: relative;
width: 360px;
height: 28px;
}
.slider-track {
width: 100%;
height: 5px;
position: absolute;
margin: auto;
top: 0;
bottom: 0;
border-radius: 5px;
}
.slider-container>input[type="range"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
position: absolute;
margin: auto;
top: 0;
bottom: 0;
width: 100%;
outline: none;
background-color: transparent;
pointer-events: none;
}
.slider-container>input[type="range"]::-webkit-slider-runnable-track {
-webkit-appearance: none;
height: 5px;
}
.slider-container>input[type="range"]::-moz-range-track {
-moz-appearance: none;
height: 5px;
}
.slider-container>input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -9px;
height: 1.7em;
width: 1.7em;
background-color: #3264fe;
cursor: pointer;
pointer-events: auto;
border-radius: 50%;
}
.slider-container>input[type="range"]::-moz-range-thumb {
-moz-appearance: none;
height: 1.7em;
width: 1.7em;
cursor: pointer;
border: none;
border-radius: 50%;
background-color: #3264fe;
pointer-events: auto;
}
.slider-container>input[type="range"]:active::-webkit-slider-thumb {
background-color: #ffffff;
border: 3px solid #3264fe;
}
<h3>Double Range Slider, Reusable Edition</h3>
<div class="slider-container">
<div class="slider-track"></div>
<input type="range" name="a" min="-130" max="-30" step="1" value="-100" autocomplete="off" />
<input type="range" name="a" min="-60" max="0" step="2" value="-30" autocomplete="off" />
</div>
<div>
<output name="a"></output> ~ <output name="a"></output>
</div>
<pre>
Changes:
1. allow different min/max/step for two inputs
2. new property 'maxGap'
3. added events 'input'/'change'
4. dropped IE/OldEdge support
</pre>
For those working with Vue, there is now Veeno available, based on noUiSlider. But it does not seem to be maintained anymore. :-(
This code covers following points
Dual slider using HTML, CSS, JS
I have modified this slider using embedded ruby so we can save previously applied values using params in rails.
<% left_width = params[:min].nil? ? 0 : ((params[:min].to_f/100000) * 100).to_i %>
<% left_value = params[:min].nil? ? '0' : params[:min] %>
<% right_width = params[:max].nil? ? 100 : ((params[:max].to_f/100000) * 100).to_i %>
<% right_value = params[:max].nil? ? '100000' : params[:max] %>
<div class="range-slider-outer">
<div slider id="slider-distance">
<div class="slider-inner">
<div inverse-left style="width:<%= left_width %>%;"></div>
<div inverse-right style="width:<%= 100 - right_width %>%;"></div>
<div range style="left:<%= left_width %>%;right:<%= 100 - right_width %>%;"></div>
<span thumb style="left:<%= left_width %>%;"></span>
<span thumb style="left:<%= right_width %>%;"></span>
<div sign style="">
Rs.<span id="value"><%= left_value.to_i %></span> to
</div>
<div sign style="">
Rs.<span id="value"><%= right_value.to_i %></span>
</div>
</div>
<input type="range" name="min" value=<%= left_value %> max="100000" min="0" step="100" oninput="
this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[1].style.width=value+'%';
children[5].style.left=value+'%';
children[7].style.left=value+'%';children[11].style.left=value+'%';
children[11].childNodes[1].innerHTML=this.value;" />
<input type="range" name="max" value=<%= right_value %> max="100000" min="0" step="100" oninput="
this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[3].style.width=(100-value)+'%';
children[5].style.right=(100-value)+'%';
children[9].style.left=value+'%';children[13].style.left=value+'%';
children[13].childNodes[1].innerHTML=this.value;" />
</div>
<div class="range-label">
<div>0</div>
<div>100000</div>
</div>
</div>
[slider] {
/*width: 300px;*/
position: relative;
height: 5px;
/*margin: 20px auto;*/
/* height: 100%; */
}
[slider] > div {
position: absolute;
left: 13px;
right: 15px;
height: 14px;
top: 5px;
}
[slider] > div > [inverse-left] {
position: absolute;
left: 0;
height: 14px;
border-radius: 3px;
background-color: #CCC;
/*margin: 0 7px;*/
margin: 0 -7px;
}
[slider] > div > [inverse-right] {
position: absolute;
right: 0;
height: 14px;
border-radius: 3px;
background-color: #CCC;
/*margin: 0 7px;*/
margin: 0 -7px;
}
[slider] > div > [range] {
position: absolute;
left: 0;
height: 14px;
border-radius: 14px;
background-color:#8950fc;
}
[slider] > div > [thumb] {
position: absolute;
top: -3px;
z-index: 2;
height: 20px;
width: 20px;
text-align: left;
margin-left: -11px;
cursor: pointer;
/* box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4); */
background-color: #FFF;
/*border-radius: 50%;*/
border-radius:2px;
outline: none;
}
[slider] > input[type=range] {
position: absolute;
pointer-events: none;
-webkit-appearance: none;
z-index: 3;
height: 14px;
top: -2px;
width: 100%;
opacity: 0;
}
div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
border: transparent;
}
div[slider] > input[type=range]:focus {
outline: none;
}
div[slider] > input[type=range]::-webkit-slider-thumb {
pointer-events: all;
width: 28px;
height: 28px;
border-radius: 0px;
border: 0 none;
background: red;
-webkit-appearance: none;
}
div[slider] > input[type=range]::-ms-fill-lower {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-fill-upper {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-tooltip {
display: none;
}
[slider] > div > [sign] {
/* opacity: 0;
position: absolute;
margin-left: -11px;
top: -39px;
z-index:3;
background-color:#1a243a;
color: #fff;
width: 28px;
height: 28px;
border-radius: 28px;
-webkit-border-radius: 28px;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;*/
color: #A5B2CB;
border-radius: 28px;
justify-content: center;
text-align: center;
display: inline-block;
margin-top: 12px;
font-size: 14px;
font-weight: bold;
}
.slider-inner{
text-align:center;
}
/*[slider] > div > [sign]:after {
position: absolute;
content: '';
left: 0;
border-radius: 16px;
top: 19px;
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top-width: 16px;
border-top-style: solid;
border-top-color:#1a243a;
}*/
[slider] > div > [sign] > span {
font-size: 12px;
font-weight: 700;
line-height: 28px;
}
[slider]:hover > div > [sign] {
opacity: 1;
}
.range-label{
display: flex;
justify-content: space-between;
margin-top: 28px;
padding: 0px 5px;
}
.range-slider-outer{
width:calc(100% - 20px);
margin:auto;
margin-bottom: 10px;
margin-top: 10px;
}

How to display custom video controls even in fullscreen

Update: Can't see to get things working in Firefox : (
How can I display custom video controls when the in fullscreen mode in modern browsers?
They disappear as soon as I go fullscreen. I'd like them to be available, and then I'll write some JavaScript to hide them on inactivity and show them once someone wiggles their mouse around.
HTML:
<video#video src="vid.mp4" preload poster="/images/poster.jpg">
<iframe src="https://youtube.com/embed/id" frameborder="0" allowfullscreen>
</video>
JS:
var bigPlayButton = document.getElementById('big-play-button')
var video = document.getElementById('video')
var playPauseButton = document.getElementById('play-pause')
var fullscreen = document.getElementById('fullscreen')
function toggleFullScreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
}
}
}
fullscreen.addEventListener('click', function (event) {
if (!video.classList.contains('fullscreen')) {
video.requestFullscreen()
} else {
document.exitFullscreen()
}
}, false)
// Detect FullScreen changes and adjust button
document.addEventListener('fullscreenchange', function (event) {
if (document.fullscreenElement) {
fullscreen.children[0].src = '/images/nofullscreen.svg'
video.classList.add('fullscreen')
} else {
fullscreen.children[0].src = '/images/fullscreen.svg'
video.classList.remove('fullscreen')
}
}, false)
CSS
video::-webkit-media-controls {
display: none !important;
}
#custom-video-controls {
z-index: 2147483648;
}
I'm using this polyfill: https://github.com/neovov/Fullscreen-API-Polyfill
Edit
The significant change was targeting the parent tag: .vidFrame for fullscreen instead of the <video> tag as per Kaido's comment.
HTML5 video's controls need special handling if you want to override them. I'm assuming you want to do that since the controls already have the full screen feature built in the controls. This demo implements:
classList for toggling the button#fullScreen states of .on and .off and button#playPause states of .play and .pause.
:fullscreen pseudo-class to insure .vidBar is on the bottom when in full screen mode.
Shadow DOM CSS Styles that are needed to override the native player's controls.
Fullscreen API vendor specific methods to enter and exit full screen mode of course.
There's no volume slider, mute button, or scrubber, just the full screen button (button#fullScreen) and play button (button#playPause). If you want them, ask another question.
Details are commented in source.
It looks as if the Snippet isn't fully functional, so here's a functional Plunker. If that version cannot be reached, then review the embedded Plunker and click the full view button:
Demo
Note: SO sandbox has changed so this demo is not fully functional go to the links mentioned previously or copy and paste the demo on a text editor.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Full Screen Video Toggle</title>
<style>
.vidFrame { position: relative; top: 10%; width: 320px; height: auto; min-height: 180px; outline: 1px dashed red; }
.vidBar { position: absolute; bottom: 0; right: 0; left: 0; height: 40px; width: 99%; }
#fullScreen { position: absolute; bottom: 0; right: 0; width: 36px; height: 36px; outline: none; border: 1px solid transparent; border-radius: 6px; display: block; cursor: pointer; }
#fullScreen:hover { border: 1px groove #0ef; }
.on, .off { background: url('https://i.imgur.com/0FTwh6M.png') no-repeat; width: 36px; height: 36px; }
.off { background-position: 0 0 }
.on { background-position: -1px -50px }
#playPause { position: absolute; bottom: 0; left: 0; width: 36px; height: 36px; background: none; font-size: 36px; color: #0ff; line-height: 1; border: 1px solid transparent; display: block; cursor: pointer; outline: none; }
#playPause.play:before { content: '\25b6'; }
#playPause.pause:before { content: '\275a\275a'; }
.vid { position: absolute; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: auto; display: block; z-index: 1; outline: 1px dotted blue; }
/*
Fullscreen Pseudo-class:
https://developer.mozilla.org/en-US/docs/Web/CSS/:fullscreen
*/
.vidBar:-moz-full-screen { position: fixed; }
.vidBar:-webkit-full-screen { position: fixed; }
.vidBar:-ms-fullscreen { position: fixed; }
.vidBar:fullscreen { position: fixed; }
/*
Special Shadow DOM Settings to Override Default Controls:
https://css-tricks.com/custom-controls-in-html5-video-full-screen/
*/
video::-webkit-media-controls-enclosure { display:none !important; }
.vidBar { z-index: 2147483648; }
</style>
</head>
<body>
<figure class="vidFrame">
<video id="vid1" class="vid" src="http://techslides.com/demos/sample-videos/small.mp4"></video>
<figcaption class="vidBar">
<button id='playPause' class="play" title="Play/Pause Video"></button>
<button id='fullScreen' class="on" title="Enter/Exit Full Screen"></button>
</figcaption>
</figure>
<script>
/*
Toggle Button with classList:
https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
*/
var fullBtn = document.getElementById('fullScreen');
var playBtn = document.getElementById('playPause');
playBtn.addEventListener('click', function(event) {
var player = document.getElementById('vid1');
if(player.paused) {
playBtn.classList.remove('play');
playBtn.classList.add('pause');
player.play();
} else {
playBtn.classList.add('play');
playBtn.classList.remove('pause');
player.pause();
}
}, false);
fullBtn.addEventListener('click', function(event) {
var tgtEle = document.querySelector('.vidFrame');
var onOrOff = fullBtn.classList.contains('on');
if (onOrOff) {
enterFS(tgtEle);
fullBtn.classList.remove('on');
fullBtn.classList.add('off');
} else {
exitFS();
fullBtn.classList.add('on');
fullBtn.classList.remove('off');
}
}, false);
/*
Fullscreen API:
https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
*/
function enterFS(element) {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
}
}
function exitFS() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
</script>
</body>
</html>
Use the Fullscreen API on the container element, not on the video
As #Kaiido says in the comments:
You have to call the enterFS method on the container element, not on
the video one.
So the answer is to use the Fullscreen API on the container element rather than the <video> element. This enables providing custom controls in that container which is now all in fullscreen.
For reference, that is the existing enterFS() function from the question:
function enterFS(element) {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
}
}
I posted this answer because I had to read the page three times to figure out what was going on here.
There is great information in #zer00ne's answer that is relevant to others with similar issues, but it doesn't directly answer #Costa's original problem, which was previously only answered in a comment.

asp .net page change background color of label when checkbox is checked

Trying to change the background color of a label for a checkbox when the checkbox is checked. I can do it using this method http://jsfiddle.net/CjpmP/ but I can find figure out how to do it with the #html.checkbox and the label for method I need to use. Here is what I've got
<style type="text/css">
.checkyoself{
display: none;
}
.checkyoself:checked + .label1{
background-color: green;
}
.label1 {
width: 240px;
height: 140px;
margin-right: 5px;
margin-left: 5px;
margin-top: 5px;
margin-bottom: 5px;
padding: 5px;
color: #ffffff;
background-color: #9e00f2;
float: left;
}
</style>
#{
var j = 0;
using (Html.BeginForm("GroceryList", "RecipeIngredient", FormMethod.Post))
{
for (int i = 0; i < #Model.RecipeItems.Count; i++)
{
var name = "check_" + j;
#Html.CheckBoxFor(itemModel => itemModel.RecipeItems[i].IsChecked, new { id = #name, #class="checkyoself" })
<label for=#name class="label1">#Model.RecipeItems[i].Recipe.Title</label>
#Html.HiddenFor(itemModel => itemModel.RecipeItems[i].Recipe.Title)
j++;
}
}
}
Try this fiddle that I made:
.check-with-label:checked + .label-for-check {
background-color:Red;
color:white;
border-style:double;
}
Just use the border property to engage background color property.

How can I make an Upvote/Downvote button?

I'm trying to make an upvote/downvote the same way that it's done on SO and Reddit, from what I can see they use arrow images as backgrounds and then position it, but I'm a CSS newbie and I need someone to walk me through it.
You could do it by adding a different picture to the background, one for every state of the button. There is however a cleaner, easier, more modern way of achieving this result: Sprites.
A sprite is an image that is saved as a part of a larger image. One of the biggest advantages of using sprites is the reduction of round-trips to the server for all the images to just one request for the Sprites. The element to display a picture has the image as background. The background is moved relative to the element so the element displays only part of the image. Like when you move a photo-frame over a poster (or in this case: moving the poster under the frame)
At SO they make an image that contains all the states for the button. They give the element for the button (a span in this case) a fixed width and height and add the background to it with CSS. Then toggle a class for the state (on or off) with javascript on the click event. Now the only thing you have to do in CSS is change the position of the background with CSS classes:
for (const btn of document.querySelectorAll('.vote')) {
btn.addEventListener('click', event => {
event.currentTarget.classList.toggle('on');
});
}
.vote {
display: inline-block;
overflow: hidden;
width: 40px;
height: 25px;
cursor: pointer;
background: url('http://i.stack.imgur.com/iqN2k.png');
background-position: 0 -25px;
}
.vote.on {
background-position: 0 2px;
}
Click to vote (using sprites): <span class="sprite vote"> </span>
You can easily add more states to the sprites like 'hover' and 'active' just the same way. SO even puts all the images for the whole page in a single image. You can verify this with firebug or the Chrome developer tools. Look for 'sprites.png'.
Update (2020)
It's been 10 years since I answered this question and in this time,
the landscape has changed. Now you can use inline svg as well to achieve this effect. I've updated the code snippet to use svg. This is how stackoverflow currently does this.
It works by toggling the color property of a surrounding span element on button click. The span element contains an inline svg image of an arrow. The fill property of the path that makes up the arrow is initialized with currentColor, which instructs it to take whatever is the current text color.
for (const btn of document.querySelectorAll('.vote')) {
btn.addEventListener('click', event => {
event.currentTarget.classList.toggle('on');
});
}
.vote {
display: inline-block;
cursor: pointer;
color: #687074
}
.vote.on {
color: #f48024
}
Click to vote (using svg):
<span class="vote">
<svg width="36" height="36">
<path d="M2 10h32L18 26 2 10z" fill="currentColor"></path>
</svg>
</span>
You can do it by using two simple images ... design two images in some image editors like Photoshop, if u don't have MSPaint...
CSS code is
#voting{
width:30px;
height:40px;
}
.upvote{
width:30px;
height: 20px;
cursor: pointer;
}
.downvote{
width:30px;
height: 20px;
background: url('downvote.jpg') 0 0 no-repeat;
cursor: pointer;
}
HTML code :
<div id="voting">
<div class="upvote"></div>
<div class="downvote"></div>
</div>
I'm doing project on django, and I'm trying to implement up-vote and down-vote on many posts, I've taken #Jan's code partly and finished it.
vote.html
<span onclick="like_function({{user_answer.pk}})" id="like-{{user_answer.pk}}" class="vote_up_off"></span>
<div id="counter-{{user_answer.pk}}">0</div>
<span onclick="dislike_function({{user_answer.pk}})" id="dislike-{{user_answer.pk}}" class="vote_down_off"></span>
vote.css
/* like dislike button */
.vote_up_off {
display: inline-block;
overflow: hidden;
width: 40px;
height: 25px;
cursor: pointer;
background: url(' https://i.stack.imgur.com/nxBdX.png');
background-position: 0 -25px;
margin-left: 5px;
}
.vote_up_on {
background-position: 0 2px;
display: inline-block;
overflow: hidden;
width: 40px;
height: 25px;
cursor: pointer;
background: url('https://i.stack.imgur.com/nxBdX.png');
margin-left: 5px;
}
.vote_down_off {
display: inline-block;
overflow: hidden;
width: 40px;
height: 25px;
cursor: pointer;
background: url('https://i.stack.imgur.com/vWw7n.png');
background-position: 0 -1px;
margin-top: 3px;
}
.vote_down_on {
display: inline-block;
overflow: hidden;
width: 40px;
height: 25px;
cursor: pointer;
background: url('https://i.stack.imgur.com/vWw7n.png');
background-position: 0 -28px;
margin-top: 3px;
}
vote.js
function like_function(answer_id) {
var like_button = document.getElementById('like-'+answer_id);
var dislike_button = document.getElementById('dislike-'+answer_id);
var counter_element = document.getElementById('counter-'+answer_id);
let current_counter = parseInt(counter_element.innerText);
//check if dislike is on(true) or off(false)
let dislike_state = false
if (dislike_button.className == "vote_down_on") {
dislike_state = true
}
else {
dislike_state = false
}
//if dislike is checked
if (dislike_state) {
current_counter += 2;
dislike_button.className = 'vote_down_off'
counter_element.innerText = current_counter
like_button.className = 'vote_up_on'
}
// if dislike is not checked
else {
if (like_button.className == 'vote_up_off') {
like_button.className = "vote_up_on"
current_counter += 1;
counter_element.innerText = current_counter
}
else {
like_button.className = "vote_up_off"
current_counter += -1;
counter_element.innerText = current_counter
}
}
}
function dislike_function(answer_id) {
var like_button = document.getElementById('like-'+answer_id);
var dislike_button = document.getElementById('dislike-'+answer_id);
var counter_element = document.getElementById('counter-'+answer_id);
let current_counter = parseInt(counter_element.innerText);
//check if like is on(true) or off(false)
let like_state = false
if (like_button.className == "vote_up_on") {
like_state = true
}
else {
like_state = false
}
//if like is checked
if (like_state) {
console.log('это тру лайк (лайк нажат)')
current_counter += -2;
like_button.className = 'vote_up_off'
counter_element.innerText = current_counter
dislike_button.className = "vote_down_on"
}
//if like is not checked
else {
if (dislike_button.className == 'vote_down_off') {
dislike_button.className = "vote_down_on"
current_counter += -1;
counter_element.innerText = current_counter
}
else {
dislike_button.className = "vote_down_off"
current_counter += 1;
counter_element.innerText = current_counter
}
}
}