Calculating the maximum/minimum height of a DIV element - javascript

The Problem:
Given a DIV element with a fixed height, which contains an unknown number of child elements that are sized relative to its height, calculate the maximum/minimum height that the DIV could resize to, without violating any of the maximum/minimum values of its child elements.
Example
Find the maximum/minimum height of DIV A
Answer
Minimum: 150px
Maximum: 275px
* {
box-sizing: border-box;
}
.border {
border-style: solid;
border-width: 1px 1px 1px 1px;
}
.A {
height: 200px;
width: 200px;
}
.B {
float: left;
width: 50%;
height: 75%;
min-height: 125px;
max-height: 225px;
background: yellow;
}
.C {
float: left;
width: 50%;
height: 75%;
min-height: 100px;
max-height: 250px;
background: green;
}
.D{
float: left;
width: 100%;
height: 25%;
min-height: 25px;
max-height: 50px;
background: blue;
}
<div class="A border">
<div class="B border">
B
</div>
<div class="C border">
C
</div>
<div class="D border">
D
</div>
</div>
Additional Information:
I currently have tried using an algorithm that traverses the DIV's DOM tree and creates an object graph representing the spacial positioning of the elements, using the elements offset. Below is a rudimentary algorithm that examines the spacial relationship of the elements, allowing for a 10px spread between edges to be considered 'touching'.
jQuery and other libraries are allowed as long as they are open source.
var _isContentRoot = function(a,b){
var aRect = a.innerRect;
var bRect = b.outerRect;
//Check if child element is a root node
return Math.abs(aRect.top - bRect.top) <= 10;
}
var _isLayoutSibling = function(a,b){
var aRect = a.outerRect;
var bRect = b.outerRect;
// If element X has a boundary that intersects element Y, and
// element X is located above element Y, element Y is a child node of
// element X
if(Math.abs(aRect.bottom - bRect.top) <= 10) {
if (aRect.left <= bRect.left && aRect.right >= bRect.left ||
aRect.left <= bRect.right && aRect.right >= bRect.right ||
aRect.left >= bRect.left && aRect.right <= bRect.right ||
aRect.left <= bRect.left && aRect.right >= bRect.right) {
return true;
}
}
return false;
}
Edit: Fixed CSS error. Here is an updated Fiddle
http://jsfiddle.net/zqnscmo2/
Edit 2: Try to think of this more of a graph problem in the problem space of CSS/HTML. Imagine the CSS and HTML are used to describe a graph where each DIV is a vertex. There exists an edge between the two vertices
1.) if the HTML element's bounding rectA.top ≈ rectB.top OR
2.) there exists an edge if the bounding rectA.bottom ≈ rectB.top
Each vertex has two exclusive sets of edges, set A contains all edges that meet criterion 1. Set B contains all edges that meet criterion 2. Therefor you can traverse the graph and find the minimal and maximal path and that should be the PARENT DIV's max/min height.
This is my proposed algorithm for determining the max/min height of the inner contents. I'm very much open to less complex solutions.

If I understood your question correctly, would this work?
// - I use two support functions that can probably be found in other JSes frameworks, and they're down below.
function calculateMySizes(someElement) {
var childDiv = findChild(someElement, "DIV");
var totalWidth = 0;
var totalHeight = 0;
var maxWidth = 0;
var maxHeight = 0;
do
{
if(childDiv.offsetLeft > maxWidth) {
maxWidth = childDiv.offsetLeft;
totalWidth += childDiv.offsetLeft;
}
if(childDiv.offsetTop > maxHeight) {
maxHeight = childDiv.offsetTop;
totalHeight += childDiv.offsetTop;
}
}
while (childDiv = nextElement(childDiv));
alert("object's current width is: " + totalWidth + " and it's child's largest width is: " + maxWidth);
alert("object's current height is: " + totalHeight + " and it's child's largest height is: " + maxHeight);
}
// - Returns the next Element of object
function nextElement(object) {
var nextObject = object;
while (nextObject = nextObject.nextSibling) {
if (nextObject.nodeType == 1) {
return nextObject;
}
}
return nextObject;
}
// - Returns the first child of elementName found
function findChild(object, elementName) {
for (var i = 0; i < object.childNodes.length; i++) {
if (object.childNodes[i].nodeType == 1) {
if (object.childNodes[i].nodeName.toUpperCase() == childName) {
return object;
}
if (object.childNodes[i].hasChildNodes()) {
var child = findChild(object.childNodes[i], childName, countMatch);
if (child) {
return child;
}
}
}
}
}
I can think of a scenario where the child object's bounding box is deceptively smaller than it's own children, in the case of a float or position:absolute element, and to fix that a recursive call for all the children would be required, but other than this scenario, this should give you the minimum width/height of any element according to their children's sizes.

This is what I'm thinking:
Find the nearest ancestor with an explicit height
Find all the ancestors with percentage heights and calculate the height of the nearest one of those ancestors to find the available height. Lets call that ancestor NAR and the height NARH.
Find the distance your element is from the top of its parent (with getBoundingClientRect). Call it DT
Subtract the top boundary of NAR from DT. Call this A.
Your maximum height should be NARH-A
Something similar could be done for the minimum.
UPDATE: Ohhhh kay, I implemented this idea and it works! There's a lot of crap it takes into account including margins, borders, padding, scroll bars (even with custom widths), percentage widths, max-height/width, and sibling nodes. Check out this code:
exports.findMaxHeight = function(domNode) {
return findMaxDimension(domNode,'height')
}
exports.findMaxWidth = function(domNode) {
return findMaxDimension(domNode,'width')
}
// finds the maximum height/width (in px) that the passed domNode can take without going outside the boundaries of its parent
// dimension - either 'height' or 'width'
function findMaxDimension(domNode, dimension) {
if(dimension === 'height') {
var inner = 'Top'
var outer = 'Bottom'
var axis = 'Y'
var otherAxis = 'X'
var otherDimension = 'width'
} else {
var inner = 'Left'
var outer = 'Right'
var axis = 'X'
var otherAxis = 'Y'
var otherDimension = 'height'
}
var maxDimension = 'max'+dimension[0].toUpperCase()+dimension.slice(1)
var innerBorderWidth = 'border'+inner+'Width'
var outerBorderWidth = 'border'+outer+'Width'
var innerPaddingWidth = 'padding'+inner
var outerPaddingWidth = 'padding'+outer
var innerMarginWidth = 'margin'+inner
var outerMarginWidth = 'margin'+outer
var overflowDimension = 'overflow'+axis
var propertiesToFetch = [
dimension,maxDimension, overflowDimension,
innerBorderWidth,outerBorderWidth,
innerPaddingWidth,outerPaddingWidth,
innerMarginWidth, outerMarginWidth
]
// find nearest ancestor with an explicit height/width and capture all the ancestors in between
// find the ancestors with heights/widths relative to that one
var ancestry = [], ancestorBottomBorder=0
for(var x=domNode.parentNode; x!=null && x!==document.body.parentNode; x=x.parentNode) {
var styles = getFinalStyle(x,propertiesToFetch)
var h = styles[dimension]
if(h.indexOf('%') === -1 && h.match(new RegExp('\\d')) !== null) { // not a percentage and some kind of length
var nearestAncestorWithExplicitDimension = x
var explicitLength = h
ancestorBottomBorder = parseInt(styles[outerBorderWidth]) + parseInt(styles[outerPaddingWidth])
if(hasScrollBars(x, axis, styles))
ancestorBottomBorder+= getScrollbarLength(x,dimension)
break;
} else {
ancestry.push({node:x, styles:styles})
}
}
if(!nearestAncestorWithExplicitDimension)
return undefined // no maximum
ancestry.reverse()
var maxAvailableDimension = lengthToPixels(explicitLength)
var nodeToFindDistanceFrom = nearestAncestorWithExplicitDimension
ancestry.forEach(function(ancestorInfo) {
var styles = ancestorInfo.styles
var newDimension = lengthToPixels(styles[dimension],maxAvailableDimension)
var possibleNewDimension = lengthToPixels(styles[maxDimension], maxAvailableDimension)
var moreBottomBorder = parseInt(styles[outerBorderWidth]) + parseInt(styles[outerPaddingWidth]) + parseInt(styles[outerMarginWidth])
if(hasScrollBars(ancestorInfo.node, otherAxis, styles))
moreBottomBorder+= getScrollbarLength(ancestorInfo.node,otherDimension)
if(possibleNewDimension !== undefined && (
newDimension !== undefined && possibleNewDimension < newDimension ||
possibleNewDimension < maxAvailableDimension
)
) {
maxAvailableDimension = possibleNewDimension
nodeToFindDistanceFrom = ancestorInfo.node
// ancestorBottomBorder = moreBottomBorder
} else if(newDimension !== undefined) {
maxAvailableDimension = newDimension
nodeToFindDistanceFrom = ancestorInfo.node
// ancestorBottomBorder = moreBottomBorder
} else {
}
ancestorBottomBorder += moreBottomBorder
})
// find the distance from the top
var computedStyle = getComputedStyle(domNode)
var verticalBorderWidth = parseInt(computedStyle[outerBorderWidth]) + parseInt(computedStyle[innerBorderWidth]) +
parseInt(computedStyle[outerPaddingWidth]) + parseInt(computedStyle[innerPaddingWidth]) +
parseInt(computedStyle[outerMarginWidth]) + parseInt(computedStyle[innerMarginWidth])
var distanceFromSide = domNode.getBoundingClientRect()[inner.toLowerCase()] - nodeToFindDistanceFrom.getBoundingClientRect()[inner.toLowerCase()]
return maxAvailableDimension-ancestorBottomBorder-verticalBorderWidth-distanceFromSide
}
// gets the pixel length of a value defined in a real absolute or relative measurement (eg mm)
function lengthToPixels(length, parentLength) {
if(length.indexOf('calc') === 0) {
var innerds = length.slice('calc('.length, -1)
return caculateCalc(innerds, parentLength)
} else {
return basicLengthToPixels(length, parentLength)
}
}
// ignores the existences of 'calc'
function basicLengthToPixels(length, parentLength) {
var lengthParts = length.match(/(-?[0-9]+)(.*)/)
if(lengthParts != null) {
var number = parseInt(lengthParts[1])
var metric = lengthParts[2]
if(metric === '%') {
return parentLength*number/100
} else {
if(lengthToPixels.cache === undefined) lengthToPixels.cache = {}//{px:1}
var conversion = lengthToPixels.cache[metric]
if(conversion === undefined) {
var tester = document.createElement('div')
tester.style.width = 1+metric
tester.style.visibility = 'hidden'
tester.style.display = 'absolute'
document.body.appendChild(tester)
conversion = lengthToPixels.cache[metric] = tester.offsetWidth
document.body.removeChild(tester)
}
return conversion*number
}
}
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/number
var number = '(?:\\+|-)?'+ // negative or positive operator
'\\d*'+ // integer part
'(?:\\.\\d*)?'+ // fraction part
'(?:e(?:\\+|-)?\\d*)?' // scientific notation
// https://developer.mozilla.org/en-US/docs/Web/CSS/calc
var calcValue = '(?:'+
'('+number+')'+ // length number
'([A-Za-z]+|%)?'+ // optional suffix (% or px/mm/etc)
'|'+
'(\\(.*\\))'+ // more stuff in parens
')'
var calcSequence = calcValue+
'((\\s*'+
'(\\*|/|\\+|-)'+
'\\s*'+calcValue+
')*)'
var calcSequenceItem = '\\s*'+
'(\\*|/|\\+|-)'+
'\\s*'+calcValue
var caculateCalc = function(calcExpression, parentLength) {
var info = calcExpression.match(new RegExp('^'+calcValue))
var number = info[1]
var suffix = info[2]
var calcVal = info[3]
var curSum = 0, curProduct = getCalcNumber(number, suffix, calcVal, parentLength), curSumOp = '+'
var curCalcExpression = calcExpression.slice(info[0].length)
while(curCalcExpression.length > 0) {
info = curCalcExpression.match(new RegExp(calcSequenceItem))
var op = info[1]
number = info[2]
suffix = info[3]
calcVal = info[4]
var length = getCalcNumber(number,suffix,calcVal, parentLength)
if(op in {'*':1,'/':1}) {
curProduct = calcSimpleExpr(curProduct,op,length)
} else if(op === '+' || op === '-') {
curSum = calcSimpleExpr(curSum,curSumOp,curProduct)
curSumOp = op
curProduct = length
}
curCalcExpression = curCalcExpression.slice(info[0].length)
}
curSum = calcSimpleExpr(curSum,curSumOp,curProduct)
return curSum
}
function calcSimpleExpr(operand1, op, operand2) {
if(op === '*') {
return operand1 * operand2
} else if(op === '/') {
return operand1 / operand2
} else if(op === '+') {
return operand1 + operand2
} else if(op === '-') {
return operand1 - operand2
} else {
throw new Error("bad")
}
}
function getCalcNumber(number, suffix, calcVal, parentLength) {
if(calcVal) {
return caculateCalc(calcVal, parentLength)
} else if(suffix) {
return basicLengthToPixels(number+suffix, parentLength)
} else {
return number
}
}
// gets the style property as rendered via any means (style sheets, inline, etc) but does *not* compute values
// domNode - the node to get properties for
// properties - Can be a single property to fetch or an array of properties to fetch
function getFinalStyle(domNode, properties) {
if(!(properties instanceof Array)) properties = [properties]
var parent = domNode.parentNode
if(parent) {
var originalDisplay = parent.style.display
parent.style.display = 'none'
}
var computedStyles = getComputedStyle(domNode)
var result = {}
properties.forEach(function(prop) {
result[prop] = computedStyles[prop]
})
if(parent) {
parent.style.display = originalDisplay
}
return result
}
// from lostsource http://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript
// dimension - either 'width' or 'height'
function getScrollbarLength(domNode, dimension) {
if(dimension === 'width') {
var offsetDimension = 'offsetWidth'
} else {
var offsetDimension = 'offsetHeight'
}
var outer = document.createElement(domNode.nodeName)
outer.className = domNode.className
outer.style.cssText = domNode.style.cssText
outer.style.visibility = "hidden"
outer.style.width = "100px"
outer.style.height = "100px"
outer.style.top = "0"
outer.style.left = "0"
outer.style.msOverflowStyle = "scrollbar" // needed for WinJS apps
domNode.parentNode.appendChild(outer)
var lengthNoScroll = outer[offsetDimension]
// force scrollbars with both css and a wider inner div
var inner1 = document.createElement("div")
inner1.style[dimension] = "120%" // without this extra inner div, some browsers may decide not to add scoll bars
outer.appendChild(inner1)
outer.style.overflow = "scroll"
var inner2 = document.createElement("div")
inner2.style[dimension] = "100%"
outer.appendChild(inner2) // this must be added after scroll bars are added or browsers are stupid and don't properly resize the object (or maybe they do after a return to the scheduler?)
var lengthWithScroll = inner2[offsetDimension]
domNode.parentNode.removeChild(outer)
return lengthNoScroll - lengthWithScroll
}
// dimension - Either 'y' or 'x'
// computedStyles - (Optional) Pass in the domNodes computed styles if you already have it (since I hear its somewhat expensive)
function hasScrollBars(domNode, dimension, computedStyles) {
dimension = dimension.toUpperCase()
if(dimension === 'Y') {
var length = 'Height'
} else {
var length = 'Width'
}
var scrollLength = 'scroll'+length
var clientLength = 'client'+length
var overflowDimension = 'overflow'+dimension
var hasVScroll = domNode[scrollLength] > domNode[clientLength]
// Check the overflow and overflowY properties for "auto" and "visible" values
var cStyle = computedStyles || getComputedStyle(domNode)
return hasVScroll && (cStyle[overflowDimension] == "visible"
|| cStyle[overflowDimension] == "auto"
)
|| cStyle[overflowDimension] == "scroll"
}
I'll probably put this in an npm/github module cause it seems like something that should be available naively, but isn't and takes a shiteload of work to do right.

Here is the best solution I could come up with.
First, if a DIV depends on it's child's contents to determine it's size, I give it an the selector .childDependent and if the div can resize vertically, I give it the selector .canResize.
<div class="A border childDependent canResize">
<div class="B border canResize">
B
</div>
<div class="C border canResize">
C
</div>
<div class="E border canResize">
E
</div>
<div class="D border canResize">
D
</div>
</div>
Here is a fiddle to look at:
http://jsfiddle.net/p8wfejhr/

Related

Word Wrap detection in JavaScript

I am trying to work out a way to detect wordwrap in a specific span tag inside a banner. If it wraps to 2 lines then increase the overall height of the container by 56px. There is also a sub headline (headline2) which also needs to increase (or decrease) the height by 40px.
I have written some basic JS code here which checks the div height of the span but its not great & also will only work for 3 lines.
// Variable banner heights
var hl11sub = 368;
var hl21sub = 448;
var hl31sub = 548;
var hl12sub = 416;
var hl22sub = 496;
var hl32sub = 576;
var hLFontSizeCSS = window.getComputedStyle(headlineText, null).getPropertyValue("font-size");
var hL2FontSizeCSS = window.getComputedStyle(headline2text, null).getPropertyValue("font-size");
var bannerHeightCSS = window.getComputedStyle(banner, null).getPropertyValue("height");
var headlineHeight = headlineText.offsetHeight;
var hL2HeadHeight = headline2text.offsetHeight;
var headHeight = headlineText.style.lineHeight = parseInt(hLFontSizeCSS) + 10 + "px";
var hL2Height = headline2text.style.lineHeight = parseInt(hL2FontSizeCSS) + 10 + "px";
// Text Height values
var hL1LineHeight = parseInt(headHeight); // 8 is line height & padding
var hL2LinesHeight = 140;
var hL3LinesHeight = 195;
// HL2 height values
var hL2TextOver1LineHeight = parseInt(hL2Height); // 8 is line height & padding
var hL2TextOver2LineHeight = 84;
if(hL2HeadHeight == hL2TextOver1LineHeight && headlineHeight == hL1LineHeight){
banner.style.height = hl11sub + "px";
}
else if(hL2HeadHeight == hL2TextOver1LineHeight && headlineHeight == hL2LinesHeight){
banner.style.height = hl21sub + "px";
}
else if(hL2HeadHeight == hL2TextOver1LineHeight && headlineHeight >= hL3LinesHeight){
banner.style.height = hl31sub + "px";
}
else if(hL2HeadHeight == hL2TextOver2LineHeight && headlineHeight == hL1LineHeight){
// Single headline with 2 lines sub
banner.style.height = hl12sub + "px";
}
else if(hL2HeadHeight == hL2TextOver2LineHeight && headlineHeight == hL2LinesHeight){
// 2 headlines with 2 lines sub
banner.style.height = hl22sub + "px";
}
else {
banner.style.height = hl32sub + "px";
// 3 headlines with 2 lines sub
It needs to only change the height of the banner depending on if the span words wrap once, twice, three times etc.
Any suggestions or help with this would be greatly appreciated.
Here is a very basic implementation on how to detect when a line is wrapped hopefully this gives you a good idea where to start and integrate it into your app.
Heres the docs for stuff used
https://developer.mozilla.org/en/docs/Web/API/EventTarget/addEventListener
https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
You mentioned the height changing and you needing to know when its wrapped you can use a mutation observer to check when the style has changed then check if its wrapped.
Resize the demo window to see results
any questions i'll try get to them asap if i've misunderstood i'll happily change :)
const h1 = document.querySelector('h1');
const banner = document.querySelector('.banner');
//handles style changes on banner to check wrapping
const observer = new MutationObserver(mutations =>
mutations.forEach(mutationRecord => onLineWrapDoSomething())
);
observer.observe(banner, { attributes : true, attributeFilter : ['style'] });
// handles window resize events
window.addEventListener('resize', onLineWrapDoSomething)
function onLineWrapDoSomething() {
const { lineHeight } = getComputedStyle(h1);
const lineHeightParsed = parseInt(lineHeight.split('px')[0]);
const amountOfLinesTilAdjust = 2;
if (h1.offsetHeight >= (lineHeightParsed * amountOfLinesTilAdjust)) {
console.log('your h1 now wrapped')
} else {
console.log('your h1 on one line')
}
}
// shows it logs when style changes and it wraps, ignore the disgusting code below
setTimeout(() => {
banner.style.width = '50%'
setTimeout(() => {
banner.style.width = '100%'
}, 1500)
}, 1500)
.banner {
width: 100%;
}
h1 {
line-height: 1.5
}
<div class="banner">
<h1>This is some text that will eventually wrap</h1>
</div>

How to check with d3.js if element is in viewpoint (in visible area)

I'm drawing large number of elements and in many situations majority of elements are outside of view point.
I'd like to avoid processing expensive rotation transformations on hidden elements.
Here's an example:
https://blockchaingraph.org/#ipfs-QmfXtMeUdjWBPQHUNKvF3nkYR57aZz7qarW5qikEUYWJvw
Many elements in this graph are hidden (try to zoom out to see). But currently I have to render each element on every tick and it's getting painfully slow.
Here's my code:
function transformLinks(svgLinks, nodeRadius, arrowSize) {
if (svgLinks) {
var link = svgLinks.selectAll('.link').filter(needRedraw);
//console.log("total:", svgLinks.selectAll('.link').size(), ',needRedraw:', link.size());
transformLinksLines(link);
transformLinksTexts(link.selectAll('.text'));
transformLinksOutlines(link, nodeRadius, arrowSize);
transformLinksOverlays(link.selectAll('.overlay'));
link.each(function (n) {
n.source.lx = n.source.x;
n.source.ly = n.source.y;
n.target.lx = n.target.x;
n.target.ly = n.target.y;
});
}
}
function needRedraw(link) {
if (!link.source) {
link = link.parentNode;
}
return nodeMoved(link.source) || nodeMoved(link.target);
}
var minDistToRedraw = 0.8;
function nodeMoved(n) {
return utils.isNumber(n.x) && utils.isNumber(n.y)
&& !(utils.isNumber(n.lx) && Math.abs(n.x - n.lx) <= minDistToRedraw && Math.abs(n.y - n.ly) <= minDistToRedraw);
}
I'd like to extend needRedraw() function to check for visibility. For now the function just checks if either linked node moved significantly enough.
Since I didn't find any out of box solution I had to get into those coordinate conversion stuff.
First, I created function that translates external container coordinates into SVG internal clients coordianate system - containerToSVG()
Then applied it on .getBoundingClientRect(); to get visible area in SVG coordinate space.
Then in the filter checking if both nodes outsize of visible area - do not redraw link.
There are possible situations when both nodes are outsize the area, but link can still cross the area. But it's not a big concern as long as user don't see link detachments.
Here's the code:
function transformLinks(svgLinks, nodeRadius, arrowSize) {
if (svgLinks) {
var containerRect = container.node().getBoundingClientRect();
var p = containerToSVG(-nodeRadius, -nodeRadius);
var r = containerToSVG(containerRect.width + nodeRadius, containerRect.height + nodeRadius);
svgVisibleRect = {left: p.x, top: p.y, right: r.x, bottom: r.y};
minDistToRedraw = (svgVisibleRect.right - svgVisibleRect.left) / (containerRect.width + nodeRadius * 2);
var link = svgLinks.selectAll('.link').filter(needRedraw);
transformLinksLines(link);
transformLinksTexts(link.selectAll('.text'));
transformLinksOutlines(link, nodeRadius, arrowSize);
transformLinksOverlays(link.selectAll('.overlay'));
link.each(function (n) {
updateLastCoord(n.source);
updateLastCoord(n.target);
});
}
}
function needRedraw(link) {
if (!nodeMoved(link.source) && !nodeMoved(link.target)) {
return false;
}
return isVisible(link.source) || isVisible(link.target);
}
function nodeMoved(n) {
return utils.isNumber(n.x) && utils.isNumber(n.y) &&
!(utils.isNumber(n.lx) && Math.abs(n.x - n.lx) <= minDistToRedraw && Math.abs(n.y - n.ly) <= minDistToRedraw);
}
function isVisible(n) {
var result = n.x > svgVisibleRect.left && n.x < svgVisibleRect.right &&
n.y > svgVisibleRect.top && n.y < svgVisibleRect.bottom;
return result;
}
function updateLastCoord(n) {
n.lx = n.x;
n.ly = n.y;
}
function containerToSVG(containerX, containerY) {
var svgPount = svgNode.createSVGPoint();
svgPount.x = containerX;
svgPount.y = containerY;
return svgPount.matrixTransform(document.getElementById("links-svg").getScreenCTM().inverse());
}
function transformLinksLines(link) {
link.attr('transform', function (d) {
var angle = rotation(d.source, d.target);
return 'translate(' + d.source.x + ', ' + d.source.y + ') rotate(' + angle + ')';
});
}

Sticky Edge - getting the edge of a cell, inside a carousel

I am working on an application that has a custom carousel and there is a desirable to intuitively move the inner contents of an item so its always in view until the item is truly out of scope.
^ so as the .item is moved in the left position. What techniques would you use to detect the edge to dynamically position the .unit padding-left value? So the text inside that cell is always viewable, even if the item starts to move out of position.
//Latest Fiddle
https://jsfiddle.net/atg5m6ym/3124/
$(document).ready(function() {
//console.log("ready!");
function isElementInViewport (el) {
//special bonus for those using jQuery
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
);
}
var currentPadding = 0;
var newPadd = 0;
function compensatePadding() {
var itemLeft = Math.abs(parseInt($('.caroseul').offset().left));
console.log("itemLeft", itemLeft)
newPadd = Math.abs(itemLeft);
$('.stick .unit').css("padding-left", newPadd + "px");
}
var unitWidth = $('.unit').width();
console.log("unitWidth", unitWidth);
function onVisibilityChange(el, callback) {
var old_visible;
return function() {
var visible = isElementInViewport(el);
if (visible != old_visible) {
old_visible = visible;
if (typeof callback == 'function') {
callback();
}
}
}
}
function checkVisible() {
console.log("checkvisible");
var labelGroups = $('.caroseul .item .wraps');
var length = labelGroups.length;
for (i = 0; i < length; i++) {
var isItemLabelInView = isElementInViewport(labelGroups[i]);
if(!isItemLabelInView){
$(labelGroups[i]).closest(".item").addClass("stick");
}
else{
$(labelGroups[i]).closest(".item").removeClass("stick");
//reset moved items
$('.unit').css("padding-left", 0);
}
console.log(" labelGroups[i]", labelGroups[i]);
console.log("isItemLabelInView", isItemLabelInView);
}
compensatePadding();
}
$('.container').on('scroll', checkVisible);
});
I tweaked a bit of your code - instead of checking if label is in viewport or not I have checked if label is moving out of viewport from left.
if ($(labelGroups[i]).offset().left < 0) {
$(labelGroups[i]).closest(".item").addClass("stick");
} else {
$(labelGroups[i]).closest(".item").removeClass("stick");
Besides this I have added a few conditions and offset values in compensatePadding() function.
Please refer this fiddle.
Using jquery for this pains me a bit, as I think you'd be better off using requestAnimationFrame...but, to answer your specific question, you could use something like this (I've specifically left the two aggregate values as separate vars in order to explain the point):
$(document).ready(function() {
function animateMe(){
$(".item").animate({
left: "-=5"
}, 1000, function() {
amountMovedLeft += 5;
if(amountMovedLeft >= amountUntilUnitHitsLeft){
$(".unit").animate({
right: "+=5"
}
}
});
}
var amountMovedLeft = 0;
var unitWidthHalf = $('.unit').width()/2;
var itemWidthHalf = $('.item').width()/2;
var amountUntilUnitHitsLeft = itemWidthHalf - unitWidthHalf;
setInterval(function(){
animateMe();
}, 1000);
});

how can I combine these two jquery calculations?

I have some inputs which has the foot/inch of two widths and two heights. I have to convert them into all foot and get the square footage.
example, width is 1'2" and height is 1'2". Another width is 2'1" and height is 2'1".
I have to convert the width into foot then times the height for both of them and then multiply the first width and height then addition to the 2nd width and height. Also, each side will need an addition 1.6 multiplication on top.
I have done it with the first width and height but I figured doing it for the second one is duplicating but don't have much idea what is the best way to combine them together since.
I have something like this for my js
$('.total-footage').on('click', function(){
/*Side A Calculation*/
var $sideAFootValueW = parseInt($('.dimensions-side-a .dimension-width .dimension-input-foot').val());
var $sideAInchValueW = parseInt($('.dimensions-side-a .dimension-width .dimension-input-inch').val());
if(($sideAFootValueW == "") || (!$.isNumeric($sideAFootValueW))){
$sideAFootValueW = 0;
}
if(($sideAInchValueW == "") || (!$.isNumeric($sideAInchValueW))){
$sideAInchValueW = 0;
}else{
$sideAInchValueW = $sideAInchValueW/12;
}
var totalFootAW = $sideAFootValueW + $sideAInchValueW;
var $sideAFootValueH = parseInt($('.dimensions-side-a .dimension-height .dimension-input-foot').val());
var $sideAInchValueH = parseInt($('.dimensions-side-a .dimension-height .dimension-input-inch').val());
if(($sideAFootValueH == "") || (!$.isNumeric($sideAFootValueH))){
$sideAFootValueH = 0;
}
if(($sideAInchValueH == "") || (!$.isNumeric($sideAInchValueH))){
$sideAInchValueH = 0;
}else{
$sideAInchValueH = $sideAInchValueH/12;
}
var totalFootAH = $sideAFootValueH + $sideAInchValueH;
var sideATotal = totalFootAH * totalFootAW * 1.6;
/*Side B Calculation*/
var $sideBFootValueW = parseInt($('.dimensions-side-b .dimension-width .dimension-input-foot').val());
var $sideBInchValueW = parseInt($('.dimensions-side-b .dimension-width .dimension-input-inch').val());
if(($sideBFootValueW == "") || (!$.isNumeric($sideBFootValueW))){
$sideBFootValueW = 0;
}
if(($sideBInchValueW == "") || (!$.isNumeric($sideBInchValueW))){
$sideBInchValueW = 0;
}else{
$sideBInchValueW = $sideBInchValueW/12;
}
var totalFootBW = $sideBFootValueW + $sideBInchValueW;
var $sideBFootValueH = parseInt($('.dimensions-side-b .dimension-height .dimension-input-foot').val());
var $sideBInchValueH = parseInt($('.dimensions-side-b .dimension-height .dimension-input-inch').val());
if(($sideBFootValueH == "") || (!$.isNumeric($sideBFootValueH))){
$sideBFootValueH = 0;
}
if(($sideBInchValueH == "") || (!$.isNumeric($sideBInchValueH))){
$sideBInchValueH = 0;
}else{
$sideBInchValueH = $sideBInchValueH/12;
}
var totalFootBH = $sideBFootValueH + $sideBInchValueH;
var sideBTotal = totalFootBH * totalFootBW * 1.6;
$('.total').empty();
$('.total').append(sideATotal+sideBTotal);
});
I figured I should be have a function so this calculation can be done easily even if there are more sides I need to calculate
Add the two helped functions to your code
function getSideDimensions($side){
var width = getDimension($side.find('.dimension-width'));
var height = getDimension($side.find('.dimension-height'));
return width * height * 1.6;
}
function getDimension($dimension){
var feet = $dimension.find('.dimension-input-foot');
var inches = $dimension.find('.dimension-input-inch');
if((feet == "") || (!$.isNumeric(feet))){
feet = 0;
}
if((inches == "") || (!$.isNumeric(inches))){
inches = 0;
}else{
inches = inches/12;
}
return feet + inches;
}
And alter your code to
$('.total-footage').on('click', function(){
var sideATotal = getSideDimensions($('.dimensions-side-a'));
var sideBTotal = getSideDimensions($('.dimensions-side-b'));
$('.total').empty();
$('.total').append(sideATotal+sideBTotal);
}

JS function that scrolls an element into view taking into account possible scrollable and positioned parent

I was looking for a function that would scroll a given element into view with some smart behavior:
if an element is descendant of a scrollable element - that ancestor is scrolled rather than body.
if an element is descendant of a positioned element - body won't be scrolled.
I didn't find any suitable function, so I made one and wanted some expert opinion on it. Please check the plunkr http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview . There are problems with animated scroll in FF, so please use Chrome to check the logic.
To illustrate, what I'm looking for - here is the first update that came to mind - if we reached an element that can scroll, lets call it SC (Scroll Parent), we should not only scroll SC to make the target visible inside it, but also recursively scroll SC itself into view, since it may outside of the currently visible are of the page. Here is the update plunkr http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview (also applied fix for FF scrolling problem).
And here is the code of the function
function scrollTo(target){
//Position delta is used for scrollable elements other than BODY
var combinedPositionDelta = 0;
var previousParent = $(target);
var parent = $(target).parent();
while(parent){
combinedPositionDelta += previousParent.position().top - parent.position().top;
//If we reached body
if(parent.prop("tagName").toUpperCase() == "BODY"){
scrollBody(target.offset().top);
break;
}
//if we reached an element that can scroll
if(parent[0].scrollHeight > parent.outerHeight()){
scrollElementByDelta(parent,combinedPositionDelta);
//Recursively scroll parent into view, since it itself might not be visible
scrollTo(parent);
break;
}
//if we reached a apositioned element - break
if(parent.css('position').toUpperCase() != 'STATIC'){
console.log("Stopping due to positioned parent " + parent[0].outerHTML);
break;
}
previousParent = parent;
parent = parent.parent();
}
}
var offsetSkin = 20;
function scrollElementByDelta(element,offsetDelta){
$(element).animate({
scrollTop: element.scrollTop() + (offsetDelta - offsetSkin)
}, 1000);
}
function scrollBody(offset){
$('body,html').animate({
scrollTop: offset - offsetSkin
}, 1000);
}
Well I'm Using this one which works very well for me:
function scrollIntoView (element, alignTop) {
var document = element.ownerDocument;
var origin = element, originRect = origin.getBoundingClientRect();
var hasScroll = false;
var documentScroll = this.getDocumentScrollElement(document);
while (element) {
if (element == document.body) {
element = documentScroll;
} else {
element = element.parentNode;
}
if (element) {
var hasScrollbar = (!element.clientHeight) ? false : element.scrollHeight > element.clientHeight;
if (!hasScrollbar) {
if (element == documentScroll) {
element = null;
}
continue;
}
var rects;
if (element == documentScroll) {
rects = {
left : 0,
top : 0
};
} else {
rects = element.getBoundingClientRect();
}
// check that elementRect is in rects
var deltaLeft = originRect.left - (rects.left + (parseInt(element.style.borderLeftWidth, 10) | 0));
var deltaRight = originRect.right
- (rects.left + element.clientWidth + (parseInt(element.style.borderLeftWidth, 10) | 0));
var deltaTop = originRect.top - (rects.top + (parseInt(element.style.borderTopWidth, 10) | 0));
var deltaBottom = originRect.bottom
- (rects.top + element.clientHeight + (parseInt(element.style.borderTopWidth, 10) | 0));
// adjust display depending on deltas
if (deltaLeft < 0) {
element.scrollLeft += deltaLeft;
} else if (deltaRight > 0) {
element.scrollLeft += deltaRight;
}
if (alignTop === true && !hasScroll) {
element.scrollTop += deltaTop;
} else if (alignTop === false && !hasScroll) {
element.scrollTop += deltaBottom;
} else {
if (deltaTop < 0) {
element.scrollTop += deltaTop;
} else if (deltaBottom > 0) {
element.scrollTop += deltaBottom;
}
}
if (element == documentScroll) {
element = null;
} else {
// readjust element position after scrolls, and check if vertical scroll has changed.
// this is required to perform only one alignment
var nextRect = origin.getBoundingClientRect();
if (nextRect.top != originRect.top) {
hasScroll = true;
}
originRect = nextRect;
}
}
}
}
I hope this helps.
If you do not mind venturing into jQuery, the scrollTo plugin is the best bet. It handles most needs and gives a very refined smooth trasition.
Hope it helps.

Categories