I use some jQuery code for my range slider, it works well on PC but not working on a touch device and I don't know how to fix it.
I have tried different ways such as:
_ Use on('touchmove', rangeInputChangeEventHandler); instead of on('input', rangeInputChangeEventHandler); but it still not working
_ Add $('input[type="range"]').on( 'touchstart', rangeInputChangeEventHandler); below $('input[type="range"]').on( 'input', rangeInputChangeEventHandler); line and it's still not working.
Here is my code, it includes Javascript, jQuery, HTML, and CSS to run code:
Javascript
//main function change number of slider when dragging
(function() {
// function to round up number
function lamtronTien(number) {
// set rounded, currentForamt, tienFormat as varibale
var rounded = Math.round(number / 1000000) * 1000000;
var currentFormat = new Intl.NumberFormat("vn-VN");
var tienFormat = currentFormat.format(rounded);
//
return tienFormat;
}
//function to turn number into word
function docSoTien(number) {
// replace "," with ""
number = number.replaceAll(",", "");
// set variable ty and trieu as below
var ty = Math.pow(10, 9);
var trieu = Math.pow(10, 6);
// when number = 0 return 0
if (number === "0") {
return 0;
// else number is larger than ty
} else {
if (number >= ty) {
// set variable as floor of "number" devided by "ty"
var hangTy = Math.floor(number / ty);
// if hangTy is greater than 1 then result
if (hangTy >= 1) {
// set varibales
var hangTrieu = number % ty;
var hangTrieu = Math.floor(hangTrieu / trieu);
if (hangTrieu > 0) {
return (hangTy + " Tỷ " + hangTrieu + " Triệu");
// else
} else {
return (hangTy + " Tỷ");
}
}
} else {
// var hangTrieu = number % ty;
var hangTrieu = Math.floor(number / trieu);
if (hangTrieu > 0) {
return (hangTrieu + " Triệu");
}
}
}
}
// function to apply change when there is a change in number input
function rangeInputChangeEventHandler(e) {
// set attribute name as rangeGroup
var rangeGroup = $(this).attr('name'),
minBtn = $(this).parent().children('.min'),
maxBtn = $(this).parent().children('.max'),
// get value of range_min, range_max
range_min = $(this).parent().children('.range_min'),
range_max = $(this).parent().children('.range_max'),
minVal = parseInt($(minBtn).val()),
maxVal = parseInt($(maxBtn).val()),
origin = e.originalEvent.target.className;
// if origin
if (origin === 'min' && minVal > maxVal - 5) {
$(minBtn).val(maxVal - 5);
}
var minVal = parseInt($(minBtn).val());
$(range_min).html(docSoTien(lamtronTien(minVal)));
// if origin is max and maxVal minus 5 is smaller than minVal
if (origin === 'max' && maxVal - 5 < minVal) {
$(maxBtn).val(5 + minVal);
}
var maxVal = parseInt($(maxBtn).val());
if (docSoTien(lamtronTien(maxVal)) === '3 Tỷ') {
$(range_max).html('> 3 Tỷ');
} else {
// apply to range_max
$(range_max).html(docSoTien(lamtronTien(maxVal)));
};
}
// apply change to input type range
$('input[type="range"]').on('input', rangeInputChangeEventHandler);
})();
/* adjust */
input[type='range'] {
width: 210px;
height: 30px;
padding: 20px;
overflow: hidden;
cursor: pointer;
outline: none;
}
/* adjust */
input[type='range'],
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
background: none;
}
/* adjust */
input[type='range']::-webkit-slider-runnable-track {
width: 300px;
height: 1px;
background: #003D7C;
}
/* adjust */
input[type='range']:nth-child(2)::-webkit-slider-runnable-track {
background: none;
}
/* adjust */
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;
}
/* adjust */
input[type='range']:nth-child(1)::-webkit-slider-thumb {
z-index: 2;
}
.rangeslider {
height: 60px;
width: 210px;
display: inline-block;
margin-top: -5px;
margin-left: 20px;
}
/* adjust rangeslider input */
.rangeslider input {
position: absolute;
}
/* adjust rangeslider */
.rangeslider {
position: absolute;
}
/* adjust rangeslider span*/
.rangeslider span {
margin-top: 30px;
left: 0;
}
/* adjust rangeslider right thumb */
.rangeslider .right {
position: relative;
float: right;
margin-right: -70px;
margin-top: -3px;
}
<!-- slider -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- slider -->
<div class="slider-group">
<div class="rangeslider">
<input class="min" name="min" type="range" min="0" max="3000000000" value="0" />
<input class="max" name="max" type="range" min="0" max="3000000000" value="3000000000" />
<span class="range_min light left">0</span>
<span class="range_max light right"> > 3 Tỷ</span>
</div>
</div>
and my site: link
Can anyone tell me what is the problem and how to fix it? Thank you!
Related
I'm trying to make an image carousel with center animation. I don't want to use CSS animations, instead I'd like to use jQuery.
By pressing the 'Prev' button the animation will start. One of the slides which will be central begins to grow. I've used jQuery's animate() to animate width and height. Everything works as required except I can't understand why the animation makes the central slide jump.
I have created this sample. If you push the 'Prev' button the animation will start.
var scroll_speed = 4000;
var items_cnt = $('.mg_item').length;
var container_size = $(".main_cnt").innerWidth();
var item_avg_w = container_size / 5;
var item_center_w = ((item_avg_w / 100) * 20) + item_avg_w;
var item_center_h = (item_center_w / 16) * 9 + 30;
var item_w = ((container_size - item_center_w) / 4) - 2;
var item_h = ((item_w / 16) * 9);
var gallery_content = $('.gallery_body').html();
$('.gallery_body').html(gallery_content + gallery_content + gallery_content);
var items_offset = items_cnt * item_w + 14;
$('.gallery_body').css('left', -items_offset);
$('.mg_item').css("width", item_w);
$('.mg_item').css("height", item_h);
//$('.mg_item').css("margin-bottom", (item_center_h - item_h) / 2);
//$('.mg_item').css("margin-top", (item_center_h - item_h) / 2);
//$('.mg_item_с').css("width", item_center_w);
//$('.mg_item_с').css("height", item_center_h);
//document.documentElement.style.setProperty('--center_width', item_center_w + "px");
//document.documentElement.style.setProperty('--center_height', item_center_h + "px");
$('.main_cnt').css("height", item_center_h);
check_visible();
AssignCenter(0);
function gonext() {
AssignCenter(-1);
ZoomIn();
$('.gallery_body').animate({
left: '+=' + (item_w + 2),
}, scroll_speed, "linear", function() {
LoopSlides();
});
}
function goprev() {
AssignCenter(1);
ZoomIn();
$('.gallery_body').animate({
left: '-=' + (item_w + 2),
}, scroll_speed, "linear", function() {
LoopSlides();
});
}
function ZoomIn() {
$('.center').animate({
width: item_center_w + 'px',
height: item_center_h + 'px',
}, scroll_speed, function() {});
}
function LoopSlides() {
var cur_pos = $('.gallery_body').position().left
var left_margin = Math.abs(items_offset * 2 - item_w) * -1;
var right_margin = 0 - item_w;
if (cur_pos < left_margin) {
$('.gallery_body').css('left', -items_offset);
}
if (cur_pos >= 0) {
$('.gallery_body').css('left', -items_offset);
}
check_visible();
AssignCenter(0);
}
function check_visible() {
$('.mg_item').each(function(i, obj) {
var pos = $(this).offset().left;
if (pos < 0 || pos > container_size) {
$(this).addClass("invisible");
$(this).removeClass("active");
} else {
$(this).addClass("active");
$(this).removeClass("invisible");
}
});
}
function AssignCenter(offset) {
var center_slide = $('.active')[2 + offset];
$('.center').each(function(i, obj) {
$(this).removeClass("center");
});
$(center_slide).addClass("center");
//$(center_slide).css("width", item_center_w);
//$(center_slide).css("height", item_center_h);
}
:root {
--center_width: 0px;
--center_height: 0px;
}
.main_cnt {
background-color: rgb(255, 0, 0);
padding: 0px;
overflow: hidden;
margin: 0px;
}
.gallery_body {
width: 500%;
background-color: rgb(128, 128, 128);
position: relative;
}
.mg_item {
width: 198px;
height: 150px;
background-color: blue;
display: inline-block;
position: relative;
margin: -1px;
padding: 0px;
font-size: 120px;
}
.center {
background-color: brown;
/*width: var(--center_width) !important;
height: var(--center_height) !important;*/
}
.item_c {
width: 410px;
height: 150px;
background-color: blueviolet;
display: inline-block;
position: relative;
margin: -1px;
padding: 0px;
font-size: 120px;
}
.video-js .vjs-dock-text {
text-align: right;
}
<script src="https://code.jquery.com/jquery-2.2.0.min.js" type="text/javascript"></script>
<div class="main_cnt">
<div class="gallery_body">
<div class="mg_item">1</div>
<div class="mg_item">2</div>
<div class="mg_item">3</div>
<div class="mg_item">4</div>
<div class="mg_item">5</div>
<div class="mg_item">6</div>
<div class="mg_item">7</div>
</div>
</div>
<br><br>
<button onclick="gonext()">GONEXT</button>
<button onclick="goprev()">GOPREV</button>
<button onclick="check_visible()">CHEVIS</button>
In the custom slider i have created, the handle is moving beyond the container. But i want it to stay within the container limits. We could just do it simple by setting margin-left as offset in CSS. But My requirement is when the handle right end detect the container's end the handle should not be allowed to move anymore. Any help is appreciated. Thanks.
Demo Link: https://jsfiddle.net/mohanravi/1pbzdyyd/30/
document.getElementsByClassName('contain')[0].addEventListener("mousedown", downHandle);
function downHandle() {
document.addEventListener("mousemove", moveHandle);
document.addEventListener("mouseup", upHandle);
}
function moveHandle(e) {
var left = e.clientX - document.getElementsByClassName('contain')[0].getBoundingClientRect().left;
var num = document.getElementsByClassName('contain')[0].offsetWidth / 100;
var val = (left / num);
if (val < 0) {
val = 0;
} else if (val > 100) {
val = 100;
}
var pos = document.getElementsByClassName('contain')[0].getBoundingClientRect().width * (val / 100);
document.getElementsByClassName('bar')[0].style.left = pos + 'px';
}
function upHandle() {
document.removeEventListener("mousemove", moveHandle);
document.removeEventListener("mouseup", upHandle);
}
.contain {
height: 4px;
width: 450px;
background: grey;
position: relative;
top: 50px;
left: 40px;
}
.bar {
width: 90px;
height: 12px;
background: transparent;
border: 1px solid red;
position: absolute;
top: calc(50% - 7px);
left: 0px;
cursor: ew-resize;
}
<div class='contain'>
<div class='bar'></div>
</div>
You need to change
this
document.getElementsByClassName('bar')[0].style.left = pos + 'px';
to this
if(pos > 90){
document.getElementsByClassName('bar')[0].style.left = pos - 90 + 'px';
}
else{
document.getElementsByClassName('bar')[0].style.left = 0 + 'px';
}
since width of your bar is 90px I am subtracting 90.
See this updated fiddle
Ok I have created a stamina container that has three stamina bars within each other, and when you click on the attack button some of the stamina is taken off the first stamina bar. However, if you keep clicking on the first stamina bar until it depletes to 0 it will then start to deplete the second stamina bar and so on. Now, that is how it was intended to happen, but I'm not able get it to function that way. Here is the jsfiddle for it, whenever I click on the attack button multiple times the stamina bar is lagging up and once the stamina bar is complete it will still deplete the stamina bar and regnerate itself again. You have to try the jsfiddle to understand what I'm talking about.
HTML
<body>
<div id="container">
<div class="hBar"> <div class="health"></div></div><!--hbar -->
<div class="sBar">
<div class="s3">
<div class="s2">
<div class="s1">
</div><!--s1 -->
</div><!--s2 -->
</div><!--s3 -->
</div><!--sBar -->
<div class="attack">attack</div>
</div><!--container -->
</body>
CSS
*{ margin:0px; padding:0px;}
.hBar{width:400px; height:40px; border:1px solid grey; margin-bottom:20px;}
.health{background:green;width:100%; height:100%;}
.sBar{ width:400px; height:40px; border:1px solid grey; margin-bottom:20px;}
.s3{ width:100%; height:100%; background:red;}
.s2{width:100%; height:100%; background:orange;}
.s1{width:100%; height:100%; background:yellow;}
#container{background:white; width:80%; padding:10px; margin:auto;}
body{background:#CCC;}
.attack{ border-radius:90px; border:black solid 1px; height:75px; width:75px; text-align:center; line-height:75px;}
.attack:hover{cursor:pointer;}
Javascript
$(document).ready(function () {
// one , two, and three variables are collecting the stamina bars
var one = $('.s1');
var two = $('.s2');
var three = $('.s3');
var oneWidth = one.width();
var twoWidth = two.width();
var threeWidth = three.width();
var stam = $('.sBar').width();
var damage;
var subtractHealth;
var num;
$('.attack').click(function () {
// timer is supposed to be the variable for a setInterval function
let timer;
// damage is supposed to take away the stamina
damage = 100;
// This function is supposed to stop the interval and the animation done on the
// stamina bars
function stopAnimate() {
clearInterval(timer);
one.stop();
two.stop();
three.stop();
}
// if the first and the second stamina bar is below 0, then add subtract the width to .s3
if (oneWidth <= 0 && twoWidth <= 0) {
subtractHealth = $('.s3').width() - damage;
three.animate({
'width': subtractHealth
}, 'fast');
// if the first stamina bar is less than 0, the subtract the width of .s2
} else if (oneWidth <= 0) {
subtractHealth = $('.s2').width() - damage;
two.animate({
'width': subtractHealth
}, 'fast');
// if the first stamina bar is not below 0 then run the content in this
} else {
subtractHealth = $('.s1').width() - damage;
one.animate({
'width': subtractHealth
}, 'fast');
}
// regenerates all the three stamina bars with the animate method
function regenerate(stam1, stam2, stam3) {
stam1.animate({
'width': stam
}, 1000, function () {
if (stam1.width() == stam) {
stam2.animate({
'width': stam
}, 1000, function () {
if (stam2.width() == stam) {
stam3.animate({
'width': stam
}, 1000)
}// if stam2
});//stam2.animate
}//if stam.width()
})//stam1.animate
setTimeout(stopAnimate(), 5000); //end function
}; //end regenerate
// run setInterval and assign the method to timer
timer = setInterval(regenerate(one, two, three), 1000);
}); //end click
}); //end ready
I'm not 100% certain that i have the effect you are after, but if not I think you should be able to modify this code to get the result you seek. If you would like additional assistance, drop a comment and I will be happy to see what I can do.
var staminaMax = 1000;
var staminaCurrent = staminaMax;
var staminaHealInterval;
var staminaTick = 100;
var staminHealPerTick = 10;
var $sBar3 = $(".sBar .sBarStatus.s3");
var $sBar2 = $(".sBar .sBarStatus.s2");
var $sBar1 = $(".sBar .sBarStatus.s1");
var healStamina = function() {
staminaCurrent = Math.min(staminaCurrent + staminHealPerTick, staminaMax);
var rawPct = staminaCurrent / staminaMax;
var s1Pct = (function() {
if (rawPct <= (2 / 3)) { return 0; }
return (rawPct - (2 / 3)) / (1 / 3);
})();
var s2Pct = (function() {
if (rawPct <= (1 / 3)) { return 0; }
if (rawPct >= (2 / 3)) { return 1; }
return (rawPct - (1 / 3)) / (1 / 3);
})();
var s3Pct = (function() {
if (rawPct >= (1 / 3)) { return 1; }
return (rawPct - (0 / 3)) / (1 / 3);
})();
$sBar3.css("width", 100 * s3Pct + "%");
$sBar2.css("width", 100 * s2Pct + "%");
$sBar1.css("width", 100 * s1Pct + "%");
if (staminaCurrent >= staminaMax) {
clearInterval(staminaHealInterval);
staminaHealInterval = null;
}
};
var dingStamina = function(amount) {
staminaCurrent = Math.max(staminaCurrent - amount, 0);
if (!staminaHealInterval) {
staminaHealInterval = setInterval(healStamina, staminaTick);
}
}
$('.attack').click(function() {
dingStamina(100);
});
* {
margin: 0px;
padding: 0px;
}
.hBar {
width: 400px;
height: 10px;
border: 1px solid grey;
margin-bottom: 20px;
}
.health {
background: green;
width: 100%;
height: 100%;
}
.sBar {
width: 400px;
height: 10px;
border: 1px solid grey;
margin-bottom: 20px;
position: relative;
}
.sBar .sBarStatus {
position: absolute;
width: 100%;
height: 100%;
}
.s3 {
background: red;
}
.s2 {
background: orange;
}
.s1 {
background: yellow;
}
#container {
background: white;
width: 80%;
padding: 10px;
margin: auto;
}
body {
background: #CCC;
}
.attack {
border-radius: 90px;
border: black solid 1px;
height: 75px;
width: 75px;
text-align: center;
line-height: 75px;
}
.attack:hover {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div class="hBar">
<div class="health"></div>
</div>
<div class="sBar">
<div class="sBarStatus s3"></div>
<div class="sBarStatus s2"></div>
<div class="sBarStatus s1"></div>
</div>
<div class="attack">attack</div>
</div>
I am using the below code to shrink the font size, but it will reduce only once, I want it to reduce every time when i enter a new character.
<input type="text" value="" id="allocatedVisionBudget" onkeydown="resizetext()">
<style>
#allocatedVisionBudget {
width: 10%;
height: 93.3px;
background-color: rgba(255,255,255,0);
border: none;
font-size: 60px;
color: #7e8081;
padding: 0px;
}
</style>
<script>
function resizetext(){
var xy = document.getElementById('allocatedVisionBudget').value;
var x = document.getElementById("allocatedVisionBudget").style.fontSize=60;
//x;
xy;
var i=0;
var y=xy.length;
if(xy.length > 4) {
document.getElementById("allocatedVisionBudget").style.fontSize=x-20;
}
}
</script>
You only have one condition:
if(xy.length > 4) {
so it only resizes when you change the lenght from 4 to 5 or from 5 to 4.
Either add more conditions, or better make the fontSize calculated as a function of the text length:
document.getElementById("allocatedVisionBudget").style.fontSize=f(xy);
function resizetext(e){
var xy = document.getElementById('allocatedVisionBudget').value;
var l=xy.length;
var f = Math.ceil(80/xy.length) || 60;
console.log(e, xy, l, f);
document.getElementById("allocatedVisionBudget").style.fontSize=f + "px";
}
#allocatedVisionBudget {
width: 10%;
height: 93.3px;
background-color: rgba(255,255,255,0);
border: #f00 solid 1px;
font-size: 60px;
color: #7e8081;
padding: 0px;
}
Input: <input type="text" value="" id="allocatedVisionBudget" onkeypress="resizetext(event)" onkeydown="resizetext(event)" onkeyup="resizetext(event)">
a few things you need to do
when you set to input element font size it should wrap as string with px or % etc...
this is example:
var x = document.getElementById("allocatedVisionBudget").style.fontSize
= "60px";
when you use x-20 the x is string so you need to use the parseInt(x) function
here is a demo for your code
HTML:
<input type="text" value="" id="allocatedVisionBudget" onkeydown="resizetext()">
javascript:
window.resizetext = function() {
var xy = document.getElementById('allocatedVisionBudget').value;
var x = document.getElementById("allocatedVisionBudget").style.fontSize = "60px";
var i = 0;
var y = xy.length;
if (xy.length > 4) {
document.getElementById("allocatedVisionBudget").style.fontSize = (parseInt(x) - 20) + "px";
}
};
CSS:
#allocatedVisionBudget {
width: 100%;
height: 93.3px;
background-color: rgba(255, 255, 255, 0);
font-size: 60px;
color: #7e8081;
padding: 0px;
}
I've got a bunch of horizontal boxes containing text. The boxes are all in a horizontally scrolling container:
// generate some random data
var model = {
leftEdge: ko.observable(0)
};
model.rows = populateArray(10 + randInt(20), randRow);
ko.applyBindings(model);
$(function() {
$('.slide').on('scroll', function() {
model.leftEdge(this.scrollLeft);
})
})
function randRow() {
var events = populateArray(50 + randInt(100), randEvent);
var left = randInt(1000);
events.forEach(function(event) {
event.left = left;
left += 10 + event.width + randInt(1000);
});
return {
events: events
}
}
function randEvent() {
var word = randWord()
var width = 50 + Math.max(8 * word.length, randInt(200));
var event = {
left: 0,
width: width,
label: word
};
event.offset = ko.computed(function() {
// reposition the text to stay
// * within its container
// * fully on-screen (if possible)
var leftEdge = model.leftEdge();
return Math.max(0, Math.min(
leftEdge - event.left,
event.width - 8 * event.label.length
));
});
return event;
}
function randWord() {
var n = 2 + randInt(5);
var ret = "";
while (n-- > 0) {
ret += randElt("rmhntsk");
ret += randElt("aeiou");
}
return ret;
}
function randElt(arr) {
return arr[randInt(arr.length)];
}
function populateArray(n, populate) {
var arr = new Array(n);
for (var i = 0; i < n; i++) {
arr[i] = populate();
}
return arr;
}
function randInt(n) {
return Math.floor(Math.random() * n);
}
.slide {
max-width: 100%;
overflow: auto;
border: 5px solid black;
}
.row {
position: relative;
height: 25px;
}
.event {
position: absolute;
top: 2.5px;
border: 1px solid black;
padding: 2px;
background: #cdffff;
font-size: 14px;
font-family: monospace;
}
.event > span {
position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="slide" data-bind="foreach: rows">
<div class="row" data-bind="foreach: events">
<div class="event" data-bind="style: { left: left+'px', width: width+'px' }"><span data-bind="text:label, style: { left: offset() + 'px' }"></div>
</div>
</div>
What I'd like to do is as the user scrolls from left-to-right, reposition the text within each box that partially overlaps the left border of the visible window to keep the text as visible as possible.
Currently I'm doing this by manually repositioning each item of text.
Is there a cleaner way to do this using CSS?
A friend helped me come up with this solution.
In English, the idea is to add an overlay to each row that is positioned relatively to the frame of the scrolling box, rather than the contents.
Then we can place a label for any box that overlaps the left edge in this overlay and it will appear to smoothly move as the box underneath it scrolls.
// generate some random data
var model = {
leftEdge: ko.observable(0),
};
model.rows = populateArray(10 + randInt(20), randRow);
model.width = Math.max.apply(Math, $.map(model.rows, function(row) {
return row.width
}));
ko.applyBindings(model);
$(function() {
$('.slide').on('scroll', function() {
model.leftEdge(this.scrollLeft);
})
})
function randRow() {
var events = populateArray(50 + randInt(100), randEvent);
var left = randInt(1000);
events.forEach(function(event) {
event.left = left;
left += 10 + event.width + randInt(1000);
});
return {
events: events,
width: left
}
}
function randEvent() {
var word = randWord()
var width = 50 + Math.max(8 * word.length, randInt(200));
var event = {
width: width,
label: word,
};
event.tense = ko.computed(function() {
// reposition the text to stay#
// * within its container
// * fully on-screen (if possible)
var leftEdge = model.leftEdge();
return ['future', 'present', 'past'][
(leftEdge >= event.left) +
(leftEdge > event.left + event.width - 8 * event.label.length)
];
});
return event;
}
function randWord() {
var n = 2 + randInt(5);
var ret = "";
while (n-- > 0) {
ret += randElt("rmhntsk");
ret += randElt("aeiou");
}
return ret;
}
function randElt(arr) {
return arr[randInt(arr.length)];
}
function populateArray(n, populate) {
var arr = new Array(n);
for (var i = 0; i < n; i++) {
arr[i] = populate();
}
return arr;
}
function randInt(n) {
return Math.floor(Math.random() * n);
}
.wrapper {
position: relative;
border: 5px solid black;
font-size: 14px;
font-family: monospace;
}
.slide {
max-width: 100%;
overflow: auto;
}
.slide > * {
height: 25px;
}
.overlay {
position: absolute;
width: 100%;
left: 0;
}
.overlay .past {
display: none
}
.overlay .present {
position: absolute;
z-index: 1;
top: 5.5px;
left 0;
}
.overlay .future {
display: none
}
.row {
position: relative;
}
.event {
position: absolute;
top: 2.5px;
border: 1px solid black;
padding: 2px;
background: #cdffff;
height: 14px;
}
.event .past {
float: right;
}
.event .present {
display: none;
}
.event .future {
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="wrapper">
<div class="slide" data-bind="foreach: rows, style: { width: width + 'px' }">
<div class="overlay" data-bind="foreach: events">
<span data-bind="text:label, css: tense"></span>
</div>
<div class="row" data-bind="foreach: events">
<div class="event" data-bind="style: { left: left+'px', width: width+'px' }"><span data-bind="text:label, css: tense"></div>
</div>
</div></div>
This doesn't result in less javascript, but it does result in more efficient javascript, as class changes happen much less often than offset changes, so fewer updates to DOM elements are required.
You can avoid processing every "event" (in the above example) by doing some pre-partitioning of the horizontal space, and only updating events in the relevant partition.