I am building a tool for creating banners at a quicker and more streamlined way, instead of creating each individually.
Doing so, I have a number of different formats (such as 300x300px, 150x600px, 970x250px) and so forth.
The projects takes the container size as input, and should style and resize all elements based on the size.
Currently, it is very inefficient styling all properties individually.
An example is outlines below, however this list grows even longer for larger/more advanced banners with multiple elements.
To the question
What is the "best practice" way of styling a lot of different elements programatically, rather than setting each property individually through the style property?
As file size is key, any 3rd party libraries are not an option.
Current (inefficient) way of doing it
I am doing it this way to reduce file size, as I cannot have the styles for say 20 formats incorporated in the same file as it would grow too large.
let bannerSize = getBannerSize(window.innerWidth, window.innerHeight);
let firstFrame = document.getElementById("#frame-1");
let firstHeading = document.getElementById("#frame-1 h1");
let firstSubHeading = document.getElementById("#frame-1 h2");
let button = document.getElementById("#frame-1 button");
let secondFrame = document.getElementById("#frame-2");
if (bannerSize == 'square') {
firstFrame.style.top = 0;
firstFrame.style.left = 0;
firstFrame.style.width = '100%';
firstHeading.style.top = 50 + 'px';
firstHeading.style.left = 50 + 'px';
firstHeading.style.fontSize = 28 + 'px';
firstSubHeading.style.top = 120 + 'px';
firstSubHeading.style.left = 50 + 'px';
firstSubHeading.style.fontSize = 18 + 'px';
} else if (bannerSize == 'portrait') {
...
} else if (bannerSize == 'landscape') {
...
}
The output would then be as follows, where the same file can be used for multiple iframes and sizes:
One one way to handle this would be through a generic function such as :
function styleElem(selector, style){
(selector) ? selector.style.cssText = style : '';
return
}
It will apply inline styles to your adslots (much more easy to read/use programmatically)
Usage :
var square = document.getElementById("#frame-1")
styleElem(square , "width:300px;height:300px;margin:50px");//apply your custom css styles
var skyscraper= document.getElementById("#frame-2")
styleElem(skyscraper, "width:150px;height:600px;margin:50px");
Related
It makes sense to me that IntersectionObserver is preferred to adding scroll-based event listeners these days. Great.
However, I read here and here that a Proxy offers a more desirable way to do this and that Object.observe is perhaps even obsolete.
After reading about Proxy traps for a while, I still can't get my head around how I would use them in my imaginary use case below - or find any good references for it.
The imaginary use case:
Generate an arbitrary number of divs with random background colours.
Each div gets an IntersectionObserver.
When isIntersecting == true for any of the divs, the body's background colour changes to that of the div.
How would I even begin to think about implementing this using proxies?
Fiddle here.
It actually creates an attractive - if counterintuitive - effect.
let numberOfDivs = 5;
function createDiv(divNumber) {
// set the div's tag name, class name and a unique ID
let aDiv = document.createElement('div');
aDiv.className = 'my-divs';
aDiv.id = 'div' + divNumber;
// set a random hex bg colour for the div;
// drops a leading zero 1/16 of the time but it's not material to the example
aDiv.style.backgroundColor = '#' + Math.floor(Math.random()*16777215).toString(16);
// append the div to the body
document.body.appendChild(aDiv);
// set IntersectionObserver on the div
let observer = new IntersectionObserver(whatsIn, {threshold: 0.5});
observer.observe(aDiv);
}
// create the divs
for ( let i = 0; i < numberOfDivs; i++ ) {
let newDiv = createDiv(i);
}
// examine the IntersectionObserver output whenever isIntersecting changes
function whatsIn(payload) {
console.log("Is " + payload[0].target.id + " intersecting? " + payload[0].isIntersecting);
// change background color based on most recent isIntersecting == true
document.body.style.backgroundColor =
payload[0].isIntersecting
? payload[0].target.style.backgroundColor
: document.body.style.backgroundColor;
}
Here's a bit of code that represents the general style I've been coding my site:
!function(){
window.ResultsGrid = Class.extend(function(){
this.constructor = function($container, options){
this.items = [];
this.$container = $($container);
this.options = $.extend({}, defaultOptions, options);
this.$grid = $(
'<div class="results-grid">\
<ul></ul>\
</div>'
)
.css("margin", -this.options.spacing / 2);
this.$list = this.$grid.find("ul");
this.$container.append(this.$grid);
};
this.setItems = function(datas) {
this.$grid.addClass("display-none");
this.clear();
for (var k in datas) this.addItem(datas[k]);
this.$grid.removeClass("display-none");
};
this.addItem = function(data) {
var width = this.options.columnWidth;
var height = this.options.rowHeight;
var padding = this.options.spacing / 2;
if (this.options.columns > 0) width = (this.$container.width() - this.options.columns * this.options.spacing) / this.options.columns;
if (this.options.rows > 0) height = (this.$container.height() - this.options.rows * this.options.spacing) / this.options.rows;
if (this.options.ratio > 0) height = width / this.options.ratio;
var item = new (this.options.class)(this.$list, {
data: data,
type: this.options.type,
width: width,
height: height,
padding: padding
});
this.items.push(item);
};
this.clear = function() {
for (var k in this.items) this.items[k].destroy();
this.items.length = 0;
};
this.destroy = function() {
this.clear();
this.$grid.find("*").off();
this.$grid.remove();
}
});
var defaultOptions = {
class: ResultsItem.Game,
type: ResultsItem.Game.COMPACT,
columns:1,
rows:0,
spacing: 10,
rowHeight: 80,
ratio: 0,
columnWidth: 0
};
}();
This is something I use for lists of items, it's just a base class so it looks fairly pointless.
On my homepage I have a few of these 'ResultsGrids' and in total I have about 100 items to be added. Each of these items calls append, addClass, css, etc. to their representative jquery object about 5 times, so that's a lot of HTML fiddling before it ever renders.
Problem is, there's quite a noticable time delay as I've just come to understand I'm accessing the DOM an unnecessary amount of times by calling methods like jquery.append for each item.
The obvious solution is to do one big append for each ResultsGrid by concatenating the html strings of each item, but I wonder if there's a middle ground between this and my current approach which will perform just as well, otherwise I'll have to rewrite a lot of code.
I like to start with a $("") and append bit by bit, but obviously this isn't good performance wise because it's constantly recalculating stuff, but I don't need to know the width, height and position of everything every step of the way. Ideally I'd like to tell it to not do anything to the DOM until I tell it to. If there's no way to do this with jquery, then I'd like a library that will allow me to do this.
I've had a brief look at js templating libraries but they don't look very enticing. Angular especially.
I don't have the big picture of what you're trying to do, but, from what I understand, you could try using jquery's $.detach() function.
this.setItems = function(datas) {
//store the parent of this grid
this.parent = this.$grid.parent();
this.$grid.detach();
this.$grid.addClass("display-none");
this.clear();
//here you add your html/etc to the grid
for (var k in datas) this.addItem(datas[k]);
//reattach the grid to the parent
this.parent.append(this.$grid);
this.$grid.removeClass("display-none");
};
Doing DOM manipulation on detached elements should be much faster and provide you with an in-between solution like you were thinking.
This is a performance tip that you could find here, along with some other useful ones.
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
pos = el.style[direction].split('px')[0];
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
I'm using the simple code above to try and move an element. Now when I breakpoint on this, the value of el.style[direction] is: " ". So then when i try to do anything with it, it breaks. Why would this be? Isn't style.left supposed to return an integer?
Why would this be?
Presumably because it hasn't been set to anything.
Isn't style.left supposed to return an integer?
No. It is supposed to return a string containing the value of the CSS left property as set directly on the element (either by setting the JS property itself or by using a style attribute). It does not get a value from the cascade and it should only be an integer if the value is 0 (since all other lengths require units).
See How to get computed style of a HTMLElement if you want to get the computed value for the property rather than what I described in the previous paragraph.
style provides the original style as calculated from the CSS, not the updated and possibly dynamic style. You probably want currentStyle instead.
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
var lft = document.defaultView.getComputedStyle(el)[direction];
pos = parseFloat(lft);
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
Note: like Elliot said you'll have to get the currentStyle/computedStyle. Here's a way to make it cross-browser, however when applying styles via JS, this is one good case where some sort of framework (eg Prototype [Scriptaculous], jQuery) would be useful.
Just a comment.
In your code:
> pos = el.style[direction].split('px')[0];
> pos = parseInt(pos, 10) + 10;
The split in the first line is superfluous, in the second line parseInt will convert (say) 10px to the number 10 just as effectively (and more efficiently) than what you have.
pos = parseInt(el.style[direction], 10);
NOTE: Originally had this listed as a memory leak. After looking into this deeper, I discovered that it's not a memory issue. It's just a very slow script. Any suggestions to speed this up would be greatly appreciated.
ANOTHER NOTE: After looking into this even further, I see that FF does not support any type of CSS that formats text in overflow. There is a hack and a workaround for that hack...but that will not be a suitable solution.
I have voted for and joined the e-mail list on this particular bug at mozilla. It's almost six years old so I resolve that users will just have to deal with it for now. At least it's not a common scenario for our product.
Original post:
The script truncates the value of an element and appends '...' while its scrollWidth is greater than it's offsetWidth. (e.g. A value of "LastName, VeryLongFirstName"will change to something like "LastName, Ver...", depending on the width of the column)
var eTable = document.getElementById(this._eDiv.id + "_tbl");
//...lots of code here...
//function called that gets all cells in a table, loops through them and clips the text
addEventListenerEx(window, "load", function() {
var aCells = eTable.getElementsByTagName("DIV");
window.alert(aCells.length);
//When aCells is length of 100, we're ok...but when it's big (like 3,000) I have problems
for (var i = 0; i < aCells.length; i++){
Grid.clipText(aCells[i]);
}
}, false);
//...lots of code here...
//This is the function doing the actual clipping
Grid.clipText = function (oDiv) {
//for tooltip
var oCurDiv;
var oTagA;
var sToolTip;
if (oDiv.firstChild) {
if (oDiv.firstChild.firstChild){
oCurDiv = oDiv.firstChild;
while (oCurDiv) {
if (is.ie) {
oTagA = oCurDiv;
} else {
// there are some different between IE & FireFox.
oTagA = oCurDiv.firstChild.parentNode;
}
if (oTagA.tagName == "A") {
sToolTip = oTagA.innerHTML;
if (sToolTip.indexOf('<b>') > 0) {
sToolTip = sToolTip.replace('<b>',"");
sToolTip = sToolTip.replace('</b>',"");
}
if (sToolTip.indexOf('<B>') > 0) {
sToolTip = sToolTip.replace('<B>',"");
sToolTip = sToolTip.replace('</B>',"");
}
oTagA.parentNode.title = convertHTMLToText(sToolTip);
}
oCurDiv = oCurDiv.nextSibling;
}
} else {
oDiv.title = convertHTMLToText(oDiv.innerHTML);
}
}
//NOTE: Additional steps to take for non-IE browsers
if (!is.ie) {
var oText = oDiv;
while (oText.nodeType != 3) {
oText = oText.firstChild;
}
var sDisplayText = oText.nodeValue;
if (sDisplayText.length < 3) return;
var lastThree;
sDisplayText = sDisplayText.slice(0, parseInt(oDiv.offsetWidth / 5));
oText.nodeValue = sDisplayText + "...";
//NOTE: Bad things happen here because of this loop
while (oDiv.scrollWidth > oDiv.offsetWidth && sDisplayText != "") {
lastThree = sDisplayText.slice(-3);
sDisplayText = sDisplayText.slice(0, sDisplayText.length - 3);
oText.nodeValue = sDisplayText + "...";
}
oText.nodeValue = sDisplayText + lastThree.slice(0, 1) + "...";
while (oDiv.scrollWidth > oDiv.offsetWidth && sDisplayText != "") {
oText.nodeValue = sDisplayText + "...";
}
}
The code works. However, the problem is that it's called over and over again after a table is loaded on the page. When the table is huge (>1,500 cells), that's when the issue starts.
So, I'm really looking for a way to make this sample (particularly the WHILE loop) more efficient.
Nothing in that is going to leak by itself. You're probably leaking oText in the closure, can you show the surrounding code?
Btw, here is a vastly more efficient way of doing this:
http://jsfiddle.net/cwolves/hZqyj/
If you really want to keep doing it the way you are, you can estimate the cutoff point by taking the length of the string and multiplying it by the proportional width it needs to be...
e.g. if the string is 100 characters and it's 2x as long as it should be, cut it to 50 chars and re-check. Or you could implement a binary 'search' algorithm to get the correct length.
The work-around, and best answer to my problem came from basic arithmetic: cross multiplication
I posted my answer in a more popular stackoverflow thread discussing the topic in better detail.
Hey i am new to jquery and i was wondering whether someone could give me some help when it comes to working with variables. The below is what i have thus far. I want to find a certain divs left position and width and then do some basic maths with those variables. Sorry is this explanation is a little confusing. So just a example as to how i would create a variable from a divs width and how to check whether variables are equal to one another would be great.
$(document).ready(function(){
//Check distance from left
var p = $(".GalleryItem");
var position = p.position();
$(".LeftPosition").text( "left: " + position.left + ", top: " + position.top );
//Check width of GalleryItem
var GalleryContainer = $(".GalleryItem");
$(".WidthText").text( "innerWidth:" + GalleryContainer.innerWidth() );
//Check width of Gallery
var GalleryContainer = $("#Gallery");
$(".WidthGalleryText").text( "innerWidth:" + GalleryContainer.innerWidth() );
});
The .width() function is "recommended when width needs to be used in a mathematical calculation". It also covers windows and document rather than just divs.
var position = $('.GalleryItem').position();
var galleryItemLeft = position.left;
var galleryItemWidth = $('.GalleryItem').width();
var galleryWidth = $('#Gallery').width();
// do calculations such as
var galleryItemRight = galleryItemLeft + galleryItemWidth;
// check if one width = another
if(galleryItemWidth == galleryWidth) { ... }
For jQuery, working with return values is just like working with any return value in javascript. Set a var = to the function (expecting a return value), compare with the '==' operator. In jQuery you can also set the actual selector objects to variables as you have done with 'var p = $('.GalleryItem');' To compare selector objects you would compare them by their properties, such as position, width, color, etc.
Hope this helps.
I guess you could just do something like:
var galleryItemWidth = $(".GalleryItem").innerWidth();
var galleryWidth = $("#Gallery").innerWidth();
And then just check something like this:
if (galleryItemWidth == galleryWidth) {
// Do something
}
else {
// Do something else
}
Or maybe I misunderstood your question?