I'm using JavaScript together with a css column-width layout to calculate breakpoints at each new column in the browser window. With each new column I also increase the page margins for which I calculate a second breakpoints so they fit.
I have everything fully working using root.classList.add(myCss) and root.classList.remove(myCss) but for the page margins I would prefer to update a single css variable --page-margins instead of adding and removing css margin classes if that's possible.
In the following sample when I load the page and two columns fit in the browser window the correct --page-margin should be 16 but the Chrome inspector shows 24. It looks like when the page loads, the matchMedia event checks each media query and if it doesn't match it sets the css variable --page-margins to (cols - 1) which I only need it to do if that event previously matched.
After the page has loaded if I increase and decrease the browser width everything works correctly. One way I could achieve this is with some kind of conditional "If event unmatches..."?
document.addEventListener('DOMContentLoaded', function() {
grid();
});
// The grid system
function grid() {
const root = document.documentElement;
const columnWidth = 239;
const columnGap = 1;
const columnMin = columnWidth + columnGap;
const scrollbar = 20;
let margins = parseInt(window.getComputedStyle(root).getPropertyValue('--page-margins'), 10);
// Page margins
const marginOne = 8;
const marginTwo = 16;
const marginThree = 24;
const marginFour = 32;
mediaQuery(2);
mediaQuery(3);
mediaQuery(4);
// Media queries for columns
function mediaQuery(cols) {
let marginStyles = window.matchMedia('(min-width: ' + columns(cols) + 'px)');
marginStyles.addEventListener('change', addMargins);
addMargins(marginStyles);
function addMargins(e) {
if (e.matches) {
root.style.setProperty('--page-margins', setMargins(cols) + 'px');
margins = parseInt(window.getComputedStyle(root).getPropertyValue('--page-margins'), 10);
}
else {
root.style.setProperty('--page-margins', setMargins((cols - 1)) + 'px');
margins = parseInt(window.getComputedStyle(root).getPropertyValue('--page-margins'), 10);
}
}
}
// Return the screen width (breakpoint) at num columns
function columns(num) {
setMargins(num);
let breakpoint = (columnMin * num) - columnGap + (padding * 2) + scrollbar;
return breakpoint;
}
// Set the margin for each column number
function setMargins(num) {
if (num == 4) {
padding = marginFour;
} else if (num == 3) {
padding = marginThree;
} else if (num == 2) {
padding = marginTwo;
} else {
padding = marginOne;
}
return padding;
}
}
I figured it out shortly after posting. I needed to nest a second conditional inside the event's else statement that checks the value of the css variable --page-margins. The variable margins is referenced elsewhere in the script so I'm updating it when the event matches and unmatches.
Everything works.
function addMargins(e) {
if (e.matches) {
root.style.setProperty('--page-margins', setMargins(cols) + 'px');
margins = parseInt(window.getComputedStyle(root).getPropertyValue('--page-margins'), 10);
}
else {
if (margins == setMargins(cols)) {
root.style.setProperty('--page-margins', setMargins((cols - 1)) + 'px');
margins = parseInt(window.getComputedStyle(root).getPropertyValue('--page-margins'), 10);
}
}
}
Related
I'm working on an animation for a site and I've gotten a glitch that I can not find a solution to. I update a 's translateY based on scroll connected to an eventListener on "scroll". The issue is that the is jumping and not behaving as expected. It works sometimes and almost all the time in Safari. The issue is always present in Firefox. I created a Codepen here.
I've tried exchange translateY to top, margin-top and padding-top. I've also tried exchange Grid to Flex and even Float. I've also tried to throttle the scroll event with lodash throttle.
If you can't see the issue, I can provide a video.
Basically, here is the JS:
const move_content = (leader, follower) => {
console.log(leader.getBoundingClientRect().top);
let margin_top = 0;
if (0 > leader.getBoundingClientRect().top) {
const pixels_scrolled = leader.getBoundingClientRect().top * -1;
const pixels_scrolled_with_viewport =
pixels_scrolled +
(window.innerHeight * pixels_scrolled) / leader.offsetHeight;
let percentage_scrolled =
pixels_scrolled_with_viewport / leader.offsetHeight;
if (1 <= percentage_scrolled) {
percentage_scrolled = 1;
}
const max_margin_pixels = leader.offsetHeight - follower.offsetHeight;
margin_top =
Math.round(max_margin_pixels * percentage_scrolled * 100) / 100;
}
follower.style.transform = "translateY(" + margin_top + "px)";
};
const syncronized_scroll = () => {
const content = document.querySelector(".test > div:first-child");
const reads = document.querySelector(".test > div:last-child");
if (content.offsetHeight > reads.offsetHeight) {
move_content(content, reads);
} else {
move_content(reads, content);
}
};
window.addEventListener("scroll", syncronized_scroll);
i'm trying to change position of a child element(with varying height based on text) using range input,i would like to stop applying top position once the child div touches the bottom of the parent having fixed height.
$('#change_position').on('input', function () {
var val = +$(this).val(),
ScrSize = parseInt($('.screen').css('height')),
Stop = $('.screentip').offset().top,
h = parseInt($('.screentip').css('height')),
tofsset = Stop - h;
if (tofsset < ScrSize) {
$('.screentip').css('top', val + "%");
}
});
The height you are expecting jQuery to return is actually a string with the ending "px". You can use the function .height(). This will return you the height of the element as integer. I have tried a slightly modiefied version of your code:
$('#input').on('input', function () {
var val = parseInt($(this).val()),
ScrSize = parseInt($('#container').height()),
TxtSize = parseInt($("#text").height()),
Stop = ScrSize - TxtSize,
valInPixel = ScrSize * val / 100;
if (valInPixel < Stop) {
$('#text').css('top', val + "%");
}
else
{
$("#text").css("top", Stop + "px");
}
});
The else part will position your element to the bottom, if the number exceeds the frame. You may also have to be aware of padding and margin. This could also lead to mispositioning.
Hope this helps.
I'm making a scheduling calendar. The events are horizontal blocks (Google Cal has vertical ones). And because I have loads of events in one date I want the events to stack onto eachother without wasting any space, like this:
I did find plugins:
http://masonry.desandro.com/
http://isotope.metafizzy.co/
http://packery.metafizzy.co/
But I'm not keen on using a 30kb plugin just to do this simple thing.
To clarify: because this is a timeline, the div-events cannot move left/right, but must fit itself vertically amongst other div-events.
My own solution is a 2.6kb (commented) jQuery script, that uses absolute positioning for events and a div as a container for each row.
This script generates randomly size bars. Each new bar checks for space in each row from top-down. I'm using percentages, because that way calculating bar position against time will be easy (100% / 24h).
http://jsfiddle.net/cj23H/5/
Though works and is efficient enough, it feels bulky. You're welcome to improve.
jQuery code from my jsfiddle:
function rand() {
return Math.floor(Math.random() * 101);
}
function get_target_row(width, left) {
var i = 0;
var target_found = false;
// Loop through all the rows to see if there's space anywhere
while (target_found === false) {
// Define current row selector
var target_i = '.personnel#row' + i;
// Add row if none
if ($(target_i).length === 0) {
$('body').append('<div class="personnel" id="row' + i + '"></div>');
}
// See if there's space
if ($(target_i).children().length === 0) {
target_found = $(target_i);
} else {
var spaceFree = false;
// Check collision for each child
$(target_i).children().each(function () {
// Get left and right coordinates
var thisWidthPx = parseFloat($(this).css('width'), 10);
var thisWidthParentPx = parseFloat($(this).parent().css('width'), 10);
var thisWidth = (thisWidthPx / thisWidthParentPx * 100);
var thisLeftPx = parseFloat($(this).css('left'), 10);
var thisLeftParentPx = parseFloat($(this).parent().css('left'), 10);
var thisLeft = (thisLeftPx / thisWidthParentPx * 100);
var thisRight = thisLeft + thisWidth;
var right = left + width;
// Sexy way for checking collision
if ((right > thisLeft && right < thisRight) || (left < thisRight && left > thisLeft) || (thisLeft > left && thisLeft < right) || (thisRight < right && thisRight > left)) {
spaceFree = false;
return false;
} else {
spaceFree = true;
}
});
// If no children have collided break the loop
if (spaceFree === true) {
target_found = $(target_i);
}
}
i++;
}
// If after all the while loops target is still not found...
if (target_found === false) {
return false;
}
// Else, if target is found, return it.
return target_found;
}
// Generate random bars
for (var i = 0; i < 10; i++) {
var width = rand()/2;
var left = rand()/2;
var right = left + width;
var target = get_target_row(width, left);
target.append('<div class="block" style="width:' + width + '%;position:absolute;left:' + left + '%;background-color:rgba(' + rand() + ',' + rand() + ',' + rand() + ',0.4)" id="bar'+i+'">' + 'b' + i + '</div>');
}
CSS:
.block {
position: absolute;
background-color: rgba(200, 100, 100, 0.4);
height: 32px;
}
.personnel {
position: relative;
float: left;
width: 100%;
height: 33px;
}
I have a Javascript file that I am using to try to animate a dropdown menu. I have the "Toggle" function in that file set to run when I click on a certain div. Here's the script I'm using:
var up = true;
function Toggle(x)
{
if (up)
{
for (var i = x.offsetTop; i <= 0; i++)
{
x.style.top = i;
if (i == 0)
{
up = false;
}
}
}
else if (up == false)
{
for (var i = x.offsetTop; i >= -50; i--)
{
x.style.top = i;
if (i == -50)
{
up = true;
}
}
}
}
In the HTML div I want to animate, I have the "onclick" property set to "onclick=Toggle(this)". The first for loop works as it should (it sets the div's top offset to 0). However, the second for loop doesn't set the offsetTop. I know that the for loop is activating because I've tested it and it gives me every integer between 0 and -50. Why isn't it setting the offset position?
1) You must specify a unit to the top ie: x.style.top = i +"px"
2) Your function won't animate instead of you use a setInterval or a setTimeout
Like you asked, an example. I wouldn't do it like this for one of my project, but i kept your function to make you more familiar with the code.
I Used setTimeout instead of setInterval because setInterval must be cleared when not needed and setTimeout is just launched one time :
var Toggle = (function() { // use scope to define up/down
var up = true;
return function(element) {
var top = parseInt(element.style.top, 10); // element.offsetTop ?
if ( !top ) {
top = 0;
}
if (up) {
if (element.offsetTop < 0) { // if we are not at 0 and want to get up
element.style.top = (top+1) + "px";
setTimeout(function() { Toggle(element); }, 10); // recall the function in 10 ms
} else { // we change up value
up = !up;
}
}
else {
if (element.offsetTop > -50) {
element.style.top = (top-1) + "px";
setTimeout(function() { Toggle(element); }, 10); // recall the function in 10 ms
} else {
up=!up;
}
}
}
})();
You'd have to use x.style.top = i + 'px' as top and similar css properties must define the type (px, em, %, etc.) unless they are 0, as this is 0 in any case.
But your script would actually snap the div directly to -50px, as you do not wait between those iteration steps.
I'd recommend to use a library like jQuery to use it's animate() method.
function Toggle(obj) {
$(obj).animate({
top: parseInt($(obj).css('top')) === 0 ? '-50px' : '0px'
})
}
I have a resisable div with text inside. I want the text to scale as the div changes size.
Specifically, I want the text to have the largest possible font size that will make it fit inside the div.
Use FitText http://fittextjs.com/
I use this plugin that I made based on fitText.js, because fitText doesn't fit my needs, due to I don't know the length of the strings to resize, so the fix resize parameter of fitText don't work in all cases.
$.fn.adjustTextSize = function (set_max_size, min_size) {
min_size = min_size || 12; // if no value then set a default one
var string, width, line, initFontSize, returnFontSize, ratio;
return this.each(function() {
// Store the object
var $this = $(this);
var resizer = function () {
string = $this;
string.html('<span style="white-space: nowrap;">' + string.html() + '</span>');
width = string.width();
line = $(string.children('span'));
initFontSize = parseInt(string.css('font-size'));
ratio = width/line.width();
returnFontSize = initFontSize*ratio;
if (set_max_size && returnFontSize > initFontSize) {
returnFontSize = initFontSize;
}
if (min_size && returnFontSize < min_size) {
returnFontSize = min_size;
}
string.css('font-size',returnFontSize);
while (line.width() >= width) {
if (min_size && returnFontSize <= min_size) {
string.html(line.html());
return false;
}
string.css('font-size', --returnFontSize);
}
string.html(line.html());
}
// Call once to set.
resizer();
// Call on resize. Opera debounces their resize by default.
$(window).on('resize orientationchange', resizer);
});
};
$('.js-adjust-text').adjustTextSize(false, 12);
$('.js-adjust-text-limit').adjustTextSize(true, 30);
This plugin get two parameters:
set_max_size: boolean to limit maximum font size to its defined size in CSS
min_size: Integer number to limit minimum font size.
I hope it works for your case.
You can try like this:
If you want the height to adjust, try setting the height to auto
$("#sample").modal({
containerCss:{
backgroundColor:"#fff",
borderColor:"#0063dc",
height:450,
padding:0,
width:830
}
});
here is a little changed code above, because it is for getting width of container I made edit for heigh container adjustment.
$.fn.adjustTextSize = function (set_max_size, min_size) {
min_size = min_size || 12; // if no value then set a default one
var string, height, line, initFontSize, returnFontSize, ratio;
return this.each(function() {
// Store the object
var $this = $(this);
var resizer = function () {
string = $this;
string.html('<span>' + string.html() + '</span>');
height = string.height();
line = $(string.children('span'));
initFontSize = parseInt(string.css('font-size'));
ratio = height/line.height();
returnFontSize = (initFontSize*ratio) ;
if (set_max_size && returnFontSize > initFontSize) {
returnFontSize = initFontSize;
}
if (min_size && returnFontSize < min_size) {
returnFontSize = min_size;
}
string.css('font-size',returnFontSize);
while (line.height() >= height) {
if (min_size && returnFontSize <= min_size) {
string.html(line.html());
return false;
}
string.css('font-size', --returnFontSize);
}
string.html(line.html());
}
// Call once to set.
resizer();
// Call on resize. Opera debounces their resize by default.
$(window).on('resize orientationchange', resizer);
});
};
Hope it will useful