Please take a look at this:
http://jsfiddle.net/SHfz4/
Technically all those blue boxes are visible as in none are displayed as none so I can't use something like this:
$('.row .inner .item:visible:last');
Because that will give box 27 each time.
As you can see some boxes are visible and other's are not depending on the size of your view port, resizing will cause more boxes to go out of view or come into view.
I need a way to get the last visible item in a row. How to do this?
P.S. I have been snippets posted here on SO that show how to tell if an element is in view but some of those scripts were returning true even when they shouldn't and all of them required a specific element to be checked against, but my case requires me to just ask for the last and not specifically test a given element.
CSS:
.row { border: 1px solid red; height: 50px; overflow: hidden; }
.row .inner { width: 1000px; }
.row .inner .item { box-shadow: inset 0 0 8px blue; width: 50px; height: 50px; float: left; line-height: 50px; }
HTML:
<div class="row">
<div class="inner">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
<div class="item item-6">6</div>
<div class="item item-7">7</div>
<div class="item item-8">8</div>
<div class="item item-9">9</div>
<div class="item item-10">10</div>
<div class="item item-11">11</div>
<div class="item item-12">12</div>
<div class="item item-13">13</div>
<div class="item item-14">14</div>
<div class="item item-15">15</div>
<div class="item item-16">16</div>
<div class="item item-17">17</div>
<div class="item item-18">18</div>
<div class="item item-19">19</div>
<div class="item item-20">20</div>
<div class="item item-21">21</div>
<div class="item item-22">22</div>
<div class="item item-23">23</div>
<div class="item item-24">24</div>
<div class="item item-25">25</div>
<div class="item item-26">26</div>
<div class="item item-27">27</div>
</div>
</div>
Does this snippet do what you expect?
var items = document.querySelectorAll('div[class^=item]')
,row = items[0].offsetParent
,rightBoundary = row.clientLeft+row.clientWidth
,bottomBoundary = row.clientTop+row.clientHeight
,found = null;
for (var i=0;i<items.length;i+=1){
if (items[i].offsetLeft > rightBoundary ||
items[i].offsetTop > bottomBoundary){
found = items[i-1];
break;
}
}
// reports div.item item-17
See this fork of your jsFiddle
[edit] added a check for [invisible] items below the first row of items, see the full screen version of the jsFiddle
var timeout = '';
$(window).resize(function () {
clearTimeout(timeout);
timeout = setTimeout(function () {
var $row = $('.row'),
rWidth = $row.width(),
$item = $row.find('.item').filter(function () {
var $this = $(this),
l = $this.width() + $this.position().left;
return l >= rWidth;
}).first();
}, 60);
}).resize();
http://jsfiddle.net/EkA3K/
Related
I want to show an horizontal scroll indicator for a scrollable DIV container.
After some testing I'm pretty sure that it's not possible wit pure CSS.
I found a snippet in an answer for a similar question.
Unfortunately I couldn't figure out how to change the script to my needs.
I'm using a simple DIV container with some elements in it.
Here's my code:
<div class="container">
<div class="scroll-wrapper">
<div class="scroll-container">
<ul class="list-inline text-white text-center">
<li class="list-inline-item" style="width: 200px;">
<div class="py-5 bg-dark"><h1 class="py-5">1</h1></div>
</li>
<li class="list-inline-item" style="width: 400px;">
<div class="py-5 bg-dark"><h1 class="py-5">2</h1></div>
</li>
[....]
</ul>
</div>
</div>
<div class="scroll-indicator">
<div class="scroll-indicator-bar"></div>
</div>
<div class="d-flex justify-content-between">
<button>Prev</button>
<button>Next</button>
</div>
</div>
And the CSS:
.scroll-wrapper {
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
position: relative;
white-space: nowrap;
}
.scroll-indicator {height: 4px; width: 100%; background-color: #ddd; margin-bottom: 2rem;}
.scroll-indicator-bar {height: 4px; width: 20%; background-color: #000;}
Working example
Is there any way to animate the scrollbar indicator with CSS and/or jQuery?
EDIT: I found another good example here: https://codepen.io/mahish/pen/RajmQw
I tried to use the code in my example but the prev/next buttons doesn't work. And I also don't know how to use the scroll position to show and move a scroll indicator.
Here's the JS code from the example (change to my class names):
// duration of scroll animation
var scrollDuration = 300;
// paddles
var leftPaddle = document.getElementsByClassName('left-paddle');
var rightPaddle = document.getElementsByClassName('right-paddle');
// get items dimensions
var itemsLength = $('.item').length;
var itemSize = $('.item').outerWidth(true);
// get some relevant size for the paddle triggering point
var paddleMargin = 20;
// get wrapper width
var getMenuWrapperSize = function() {
return $('.scroll-wrapper').outerWidth();
}
var menuWrapperSize = getMenuWrapperSize();
// the wrapper is responsive
$(window).on('resize', function() {
menuWrapperSize = getMenuWrapperSize();
});
// size of the visible part of the menu is equal as the wrapper size
var menuVisibleSize = menuWrapperSize;
// get total width of all menu items
var getMenuSize = function() {
return itemsLength * itemSize;
};
var menuSize = getMenuSize();
// get how much of menu is invisible
var menuInvisibleSize = menuSize - menuWrapperSize;
// get how much have we scrolled to the left
var getMenuPosition = function() {
return $('.scroll-container').scrollLeft();
};
// finally, what happens when we are actually scrolling the menu
$('.scroll-container').on('scroll', function() {
// get how much of menu is invisible
menuInvisibleSize = menuSize - menuWrapperSize;
// get how much have we scrolled so far
var menuPosition = getMenuPosition();
var menuEndOffset = menuInvisibleSize - paddleMargin;
// show & hide the paddles
// depending on scroll position
if (menuPosition <= paddleMargin) {
$(leftPaddle).addClass('hidden');
$(rightPaddle).removeClass('hidden');
} else if (menuPosition < menuEndOffset) {
// show both paddles in the middle
$(leftPaddle).removeClass('hidden');
$(rightPaddle).removeClass('hidden');
} else if (menuPosition >= menuEndOffset) {
$(leftPaddle).removeClass('hidden');
$(rightPaddle).addClass('hidden');
}
// print important values
$('#print-wrapper-size span').text(menuWrapperSize);
$('#print-menu-size span').text(menuSize);
$('#print-menu-invisible-size span').text(menuInvisibleSize);
$('#print-menu-position span').text(menuPosition);
});
// scroll to left
$(rightPaddle).on('click', function() {
$('.scroll-container').animate( { scrollLeft: menuInvisibleSize}, scrollDuration);
});
// scroll to right
$(leftPaddle).on('click', function() {
$('.scroll-container').animate( { scrollLeft: '0' }, scrollDuration);
});
You can have your own custom horizontal scroll behavior with vanilla js, you just need to handle mousedown, mouseup and mousemove events, calculate the needed scroll value and move your elements using transform: translateX() style, and to keep track with these values,
I did some changes and added some js code, check the snippet bellow:
const scrollBar = document.getElementById('myBar');
const scrollBarWrapper = document.getElementById('barWrapper');
const scrollContent = document.getElementById('scroll-container');
scrollBar.style.width = ((scrollContent.offsetWidth * scrollBarWrapper.offsetWidth) / scrollContent.scrollWidth) + 'px';
let isScrolling = false;
let cursorX = 0;
let translateXValue = 0;
scrollBar.addEventListener('mousedown', function(e) {
e.preventDefault();
isScrolling = true;
cursorX = e.clientX;
});
document.addEventListener('mouseup', function(e) {
if (isScrolling) {
e.preventDefault();
isScrolling = false;
translateXValue += (e.clientX - cursorX);
}
});
document.addEventListener('mousemove', function(e) {
if (isScrolling && cursorX !== e.clientX) {
e.preventDefault();
const translateAmount = (translateXValue + (e.clientX - cursorX));
const scrollLength = (barWrapper.offsetWidth - scrollBar.offsetWidth);
const barScroll = Math.min(Math.max(0, translateAmount), scrollLength);
const contentTranslateRatio = (barScroll * scrollContent.scrollWidth) / scrollContent.offsetWidth;
scrollBar.style.transform = 'translateX(' + barScroll + 'px)';
scrollContent.style.transform = 'translateX(' + -contentTranslateRatio + 'px)';
}
});
.scroll-wrapper {
width: 100%;
overflow: hidden;
position: relative;
white-space: nowrap;
}
.scroll-indicator {height: 6px; width: 100%; background-color: #ddd; margin-bottom: 2rem;}
.scroll-indicator-bar {height: 6px; width: 20%; background-color: #000;}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container" id="container">
<div class="scroll-wrapper">
<div class="scroll-container" id="scroll-container">
<ul class="list-inline text-white text-center">
<li class="list-inline-item" style="width: 200px;">
<div class="py-5 bg-dark"><h1 class="py-5">1</h1></div>
</li>
<li class="list-inline-item" style="width: 400px;">
<div class="py-5 bg-dark"><h1 class="py-5">2</h1></div>
</li>
<li class="list-inline-item" style="width: 300px;">
<div class="py-5 bg-dark"><h1 class="py-5">3</h1></div>
</li>
<li class="list-inline-item" style="width: 150px;">
<div class="py-5 bg-dark"><h1 class="py-5">4</h1></div>
</li>
<li class="list-inline-item" style="width: 250px;">
<div class="py-5 bg-dark"><h1 class="py-5">5</h1></div>
</li>
<li class="list-inline-item" style="width: 300px;">
<div class="py-5 bg-dark"><h1 class="py-5">6</h1></div>
</li>
<li class="list-inline-item" style="width: 200px;">
<div class="py-5 bg-dark"><h1 class="py-5">7</h1></div>
</li>
<li class="list-inline-item" style="width: 400px;">
<div class="py-5 bg-dark"><h1 class="py-5">8</h1></div>
</li>
<li class="list-inline-item" style="width: 300px;">
<div class="py-5 bg-dark"><h1 class="py-5">9</h1></div>
</li>
</ul>
</div>
</div>
<div class="scroll-indicator" id="barWrapper">
<div class="scroll-indicator-bar" id="myBar"></div>
</div>
<div class="d-flex justify-content-between">
<button>Prev</button>
<button>Next</button>
</div>
</div>
by this code you have a dynamic scrollbar width dynamic width based on the content, and you can manage your own scroll behavior,
then, you can add custom next() and previous() functions to add translate for both scrollbar and content, as implemented in mousemove handler
I found a solution by using SimpleBar: https://github.com/Grsmto/simplebar/tree/master/packages/simplebar
I am trying to change the color of background.
I am changing every odd results to light green(#f0f5f5) so when the result ends in even number,
I get big white space.
I would like to change background color of pagination section to light green when the result ends in even number.
Sear
search results displays only 5 results so it could be 2th and 4th.
search.addWidgets([
instantsearch.widgets.searchBox({
container: '#searchbox',
}),
instantsearch.widgets.hits({
container: '#Algolia_Result',
transformItems: function (items) {
return items.map(function (item) {
if (item.objectType === 'Startup') {
item._isDescription = isNotNull(item.description);
} else if (item.objectType === 'NEWS') {
item._isSource = isNotNull(item.source);
} else if (item.objectType === 'Comment') {
item._isComment = isNotNull(item.comment);
return item;
});
},
templates: {
empty: '<div id="empty">No results have been found for {{ query }}.</div><br>',
item: `
<a href="{{linkUrl}}" target="_blank">
<div class="algolia_container">
<div class="item1">
<div id="images"><img src="{{logoUrl}}" alt="{{hits-image}}" id="hits-image"></div>
<div id="objTyeps"><span class="objectType {{objectCss}}">{{objectType}}</span></div>
</div>
<div class="item2">
<div id="objectTitle">
<span id="titleForDisplay">{{#helpers.highlight}}{ "attribute": "titleForDisplay" }{{/helpers.highlight}}</span>
</div>
</div>
<div class="item3">
{{#_isLocation}}
<div id="location">{{#helpers.highlight}}{ "attribute": "location" }{{/helpers.highlight}}</div>
{{/_isLocation}}
</div>
</div></a>
`,
},
}),
instantsearch.widgets.pagination({
container: '#pagination',
}),
]);
#Algolia_Result > div > div > ol > li:nth-child(odd){background-color: #f0f5f5;}
.ais-Pagination-item {
display:inline;
padding: 5px;
margin: 0 5px;
border: 1px solid #E8E8E8;
border-radius:5px;
font-size:18px;
}
.ais-Pagination-list {
text-align: center;
height:45px;
padding-top: 10px;
}
.ais-Pagination-item:hover {
background-color: #DCDCDC;
transition: background-color .2s;
}
.ais-Pagination-item--selected{
background-color: #E8E8E8;
}
<div id="searchbox"></div>
<div id="results">
<div id="Algolia_Result"></div>
<div id="pagination"></div>
</div>
This is ok
This need be fixed as if the background color of pagination area is the same as the last result, it must be green
This is what I get in the console.
You can color background of the pagination row by using JavaScript to count the number of results and apply color if the number of results is even.
Check out the example below.
Example 1 is with an odd number of result rows and the CSS works fine, same as your working example.
Example 2 is with an even number of result rows and uses the JS code to style the pagination background.
// Count the rows
let numRows = document.querySelectorAll('#example-2 .row').length
// If the number of rows is even
if (numRows % 2 == 0) {
// Apply the background color to the pagination row
document.querySelector('#example-2 .pagination').style.backgroundColor = '#eee'
}
.container {
border: 1px solid #000;
margin-bottom: 5px;
}
.row:nth-child(odd) {
background-color: #eee;
}
Example 1
<div id="example-1" class="container">
<div>
<div class="row">Row 1</div>
<div class="row">Row 2</div>
<div class="row">Row 3</div>
</div>
<div>
<div class="pageination">Pagination Row</div>
</div>
</div>
Example 2
<div id="example-2" class="container">
<div>
<div class="row">Row 1</div>
<div class="row">Row 2</div>
<div>
<div>
<div class="pagination">Pagination Row</div>
</div>
</div>
EDIT: So in your example, you would add the following JavaScript.
<script>
let numRows = document.querySelectorAll('.ais-Hits-item').length
if (numRows % 2 == 0) {
document.querySelector('.ais-Pagination-list').style.backgroundColor = '#eee'
}
</script>
EDIT 2: Looking at your code sandbox I can see that the issue is that the JS that counts the number of rows is being run before the rows have been rendered by Algolia.
To solve this issue we need to place our row counting JS into an Algolia callback that is ran after the rows have been rendered. We can use the algolia search.on('render', ...) event callback.
Try this:
search.on('render', () => {
let numRows = document.querySelectorAll('.algolia_container').length;
if (numRows % 2 === 0) {
document.querySelector('#pagination').style.backgroundColor = 'red';
} else {
document.querySelector('#pagination').style.backgroundColor = 'transparent';
}
});
Trying to a create a timeline box with scroll that has overflown items. When scrolling all items fully visible should get the visible class and whenever a new item becomes fully visible should get the visible class. Im missing some math or logic here, to make it accurate. Any tips or alternative methods would be very helpful.
Codepen: https://codepen.io/rKaiser/pen/BaKZgoX
<div id="wrapper" class="history">
<div class="history-inner">
<div class="item i1 wide"></div>
<div class="item i2"></div>
<div class="item i3 wide"></div>
<div class="item i4"></div>
<div class="item i5"></div>
<div class="item i6 wide"></div>
<div class="item i7"></div>
<div class="item i8"></div>
<div class="item i9"></div>
<div class="item i10 wide"></div>
<div class="item i11"></div>
<div class="item i12"></div>
<div class="item i13"></div>
<div class="item i14 wide"></div>
<div class="item i15"></div>
<div class="item i16 wide"></div>
<div class="item i17"></div>
<div class="item i18"></div>
<div class="item i19 wide"></div>
<div class="item i20"></div>
<div class="item i21 wide"></div>
<div class="item i22"></div>
<div class="item i23"></div>
</div>
</div>
.history {
position: relative;
overflow-y: hidden;
}
.history-inner {
display: inline-flex;
}
.item {
width:120px;
height:300px;
display:inline-block;
border:1px solid #ccc;
background: #777;
transition: all .3s ease;
&.wide {
width:170px;
}
&.visible {
background: green !important;
}
}
.item:nth-child(even){
background: #666;
}
js
var historyW = $('.history').outerWidth();
var historyInnerW = $('.history-inner').outerWidth();
var currentHiddenItem = historyW + 5; // bigger than history
var historyPxScr = $('.history').scrollLeft();
var entryNum = 1;
var itemMargin = 0;
$('.history').on('resize scroll', function() {
historyW = $('.history').outerWidth();
historyPxScr = $('.history').scrollLeft();
console.log( historyW + historyPxScr + ' sum');
console.log( currentHiddenItem + ' curr');
// console.log( historyInnerW + ' iii');
if ( historyW + historyPxScr < currentHiddenItem - itemMargin) {
$('.item.i'+entryNum).addClass('visible');
} else {
currentHiddenItem += $('.item.i'+entryNum).outerWidth();
itemMargin = $('.item.i'+entryNum).outerWidth();
//console.log(itemMargin + 'IM');
entryNum += 1;
console.log(entryNum);
console.log(currentHiddenItem);
}
});
I would go for something like this, tried to do it in the jQuery way ;-).
var isFullyVisible = function(elem) {
return elem.getBoundingClientRect().right - window.innerWidth < 0;
}
var checkItemVisibility = function () {
$('.history .item').each(function (index, elem) {
if (isFullyVisible(elem)) {
$(elem).addClass('visible');
} else {
$(elem).removeClass('visible');
}
});
}
$(document).ready(function() {
checkItemVisibility();
$('.history').on('scroll', checkItemVisibility);
});
Tested this code in your codepen and it worked.
Currently, I'm having a list of tabs visible horizontally. On click of tab it should show me the complete text tab.
Expected Behavior:
When i click on the partly visible tab, it should show me the complete tab by moving left side, same thing should be applicable if I'm doing it from right side(Scroll right if it is first tab).
Text content is dynamic, it can have any number of words. We need to fit it dynamically.
Example:
https://jsfiddle.net/kzLexmh9/1/
In my current example "HelloWorldFour" is partially displaying. OnClick it should show me the full text by moving the scroll content to left.
HTML:
<div class="group" id="frames">
<div class="item frames-item">HelloWorldOne</div>
<div class="item frames-item">HelloWorldTwo</div>
<div class="item frames-item">HelloWorldThree</div>
<div class="item frames-item">HelloWorldFour</div>
<div class="item frames-item">HelloWorldFive</div>
<div class="item frames-item">HelloWorldSix</div>
<div class="item frames-item">HelloWorldSeven</div>
<div class="item frames-item">HelloWorldEight</div>
<div class="item frames-item">HelloWorldNine</div>
<div class="item frames-item">HelloWorldTen</div>
<div class="item frames-item">HelloWorldEleven</div>
<div class="item frames-item">HelloWorldTwelve</div>
</div>
Script:
$("#frames").on('click', function (e) {
if (e.target.classList.contains('item')) {
e.target.parentNode.scrollLeft = e.target.offsetLeft;
}
});
CSS:
body {
background: red;
}
.group{
width: 400px;
display: flex;
position: relative; /* This is important for the js offsetLeft property */
overflow-x: scroll;
}
.item{
width: 200px;
background: #eee;
margin-right: 10px;
}
Is something like this what you're looking for? This will work on any of the "HelloWorld" boxes..
EDIT:
So I was finally able to get this to work... It is essentially a two part process.. I have added examples to the demo snippet showing how each step works.
How this works:
Calculate the position to scroll the div (that we just clicked) all the way to the left side of the parent container (this was my previous answer)
Calculate the 'offset' needed to scroll the div (that we just clicked) back to the right
Scroll to that position
These calculations are determined before any scrolling is performed, so it is a seamless scroll - the calculation is a two part process, but the scrolling is done using the final calculation.
There are 3 demos in this snippet, make sure to scroll down all the way to test them all.
$(".item1").on('click', function(event) {
finalSolution(this, "#frames1", 400);
});
$(".item2").on('click', function(event) {
zeroLeftOnly(this, "#frames2", 800);
});
$(".item3").on('click', function(event) {
animatedSteps(this, "#frames3", 800);
});
function finalSolution(element, container, scrollSpeed) {
const zeroLeft = $(element).position().left + $(container).scrollLeft();
const offsetLeft = $(container).width() - $(element).width();
const scrollTo = zeroLeft - offsetLeft;
$(container).animate({ scrollLeft: scrollTo }, scrollSpeed);
}
function zeroLeftOnly(element, container, scrollSpeed) {
const zeroLeft = $(element).position().left + $(container).scrollLeft();
$(container).animate({ scrollLeft: zeroLeft }, scrollSpeed);
}
function animatedSteps(element, container, scrollSpeed) {
const zeroLeft = $(element).position().left + $(container).scrollLeft();
$(container).animate({ scrollLeft: zeroLeft }, scrollSpeed);
const offsetLeft = $(container).width() - $(element).width();
const scrollTo = zeroLeft - offsetLeft;
setTimeout(() => {
$(container).animate({ scrollLeft: scrollTo }, scrollSpeed);
}, 1000);
}
body {
background: red;
}
h3{
color: white;
text-decoration: underline white;
margin: 5px;
}
.group {
border: 1px solid black;
padding: 5px 0px;
width: 400px;
height: 38px;
display: flex;
position: relative;
/* This is important for the js offsetLeft property */
overflow-x: scroll;
}
.item1,
.item2,
.item3 {
width: 200px;
background: #eee;
margin-right: 10px;
cursor: pointer;
}
.clickme {
background: lightblue;
}
p {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h3>Final Solution</h3>
<p><i><b><small>Click on 'HelloWorldFour' (the light blue block)</small></b></i></p>
<div class="group" id="frames1">
<div class="item1 frames-item">HelloWorldOne</div>
<div class="item1 frames-item">HelloWorldTwo</div>
<div class="item1 frames-item">HelloWorldThree</div>
<div class="clickme item1 frames-item">HelloWorldFour</div>
<div class="item1 frames-item">HelloWorldFive</div>
<div class="item1 frames-item">HelloWorldSix</div>
<div class="item1 frames-item">HelloWorldSeven</div>
<div class="item1 frames-item">HelloWorldEight</div>
<div class="item1 frames-item">HelloWorldNine</div>
<div class="item1 frames-item">HelloWorldTen</div>
<div class="item1 frames-item">HelloWorldEleven</div>
<div class="item1 frames-item">HelloWorldTwelve</div>
</div><br /><hr />
<h3>Each step is animated here - to show how the underlying calculation works.</h3>
<p><i><b><small>Click on 'HelloWorldFour' (the light blue block)</small></b></i></p>
<div class="group" id="frames3">
<div class="item3 frames-item">HelloWorldOne</div>
<div class="item3 frames-item">HelloWorldTwo</div>
<div class="item3 frames-item">HelloWorldThree</div>
<div class="clickme item3 frames-item">HelloWorldFour</div>
<div class="item3 frames-item">HelloWorldFive</div>
<div class="item3 frames-item">HelloWorldSix</div>
<div class="item3 frames-item">HelloWorldSeven</div>
<div class="item3 frames-item">HelloWorldEight</div>
<div class="item3 frames-item">HelloWorldNine</div>
<div class="item3 frames-item">HelloWorldTen</div>
<div class="item3 frames-item">HelloWorldEleven</div>
<div class="item3 frames-item">HelloWorldTwelve</div>
</div><br /><hr />
<h3>Using only 'zeroLeft' - this was my first, older, answer.</h3>
<p><i><b><small>Click on 'HelloWorldFour' (the light blue block)</small></b></i></p>
<div class="group" id="frames2">
<div class="item2 frames-item">HelloWorldOne</div>
<div class="item2 frames-item">HelloWorldTwo</div>
<div class="item2 frames-item">HelloWorldThree</div>
<div class="clickme item2 frames-item">HelloWorldFour</div>
<div class="item2 frames-item">HelloWorldFive</div>
<div class="item2 frames-item">HelloWorldSix</div>
<div class="item2 frames-item">HelloWorldSeven</div>
<div class="item2 frames-item">HelloWorldEight</div>
<div class="item2 frames-item">HelloWorldNine</div>
<div class="item2 frames-item">HelloWorldTen</div>
<div class="item2 frames-item">HelloWorldEleven</div>
<div class="item2 frames-item">HelloWorldTwelve</div>
</div>
You are just nearby solution. Just try this it to display clicked item at center of group div.
e.target.parentNode.scrollLeft = e.target.offsetLeft - e.target.parentNode.offsetWidth/2 ;
I have a div list that looks like this:
<div class="item"></div>
<div class="item"></div>
<div class="item"></div> <!--the middle one-->
<div class="item"></div>
<div class="item"></div>
I need to determine which div is in the middle of the list, please note that the div number is dynamic, depends on user's input. my final goal is to determine which divs are on the left and right side of the "middle div" then apply a class depends on its position.
The final result should look like this:
<div class="item left"></div>
<div class="item left"></div>
<div class="item center"></div> <!--the middle one-->
<div class="item right"></div>
<div class="item right"></div>
I was thinking to add a number identifier for each div and use median to determine the "middle div" but I'm not quite sure.
Perhaps there is a better approach for this problem using javascript, jquery or even pure css?
Update:
Additional information for handling even number:
in case the list has even number of child divs, it should divide it like this
<div class="item left"></div>
<div class="item left"></div>
<div class="item left"></div>
<div class="item right"></div>
<div class="item right"></div>
<div class="item right"></div>
in my problem, both Rory McCrossan and user3297291 works well. I added some modification to both of it for handling even numbers.
Rory McCrossan's (with JQuery):
var $items = $('.item');
var middleIndex = Math.floor($items.length / 2);
var hasMid = $items.length % 2;
console.log(middleIndex);
if(hasMid == 1){
$items.eq(middleIndex).addClass('middle')
.prevAll().addClass('left').end()
.nextAll().addClass('right');
}
if(hasMid == 0){
$items.eq(middleIndex).addClass('right')
.prevAll().addClass('left').end()
.nextAll().addClass('right');
}
user3297291's :
var setMidClasses = function (elementList, beforeMid, atMid, afterMid) {
var i = 0,
hasMid = elementList.length % 2,
mid = Math.floor(elementList.length / 2);
while (i < mid) {
elementList[i].classList.add(beforeMid);
i += 1;
}
if (hasMid == 1) {
elementList[i].classList.add(atMid);
i += 1;
}
while (i < elementList.length) {
elementList[i].classList.add(afterMid);
i += 1;
}
};
setMidClasses(document.querySelectorAll(".item"),
"left", "middle", "right");
feel free to edit the code snippets as it might be not very tidy after my edits.
In the case of an odd number of items you can get the middle item using Math.floor(items.length / 2). From there you can use prevAll() and nextAll() to add the classes to the relevant elements:
var $items = $('.item');
var middleIndex = Math.floor($items.length / 2);
$items.eq(middleIndex).addClass('center')
.prevAll().addClass('left').end()
.nextAll().addClass('right');
.left { color: red; }
.center { color: green; }
.right { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div> <!--the middle one-->
<div class="item">4</div>
<div class="item">5</div>
Without jQuery you might as well do like this;
var els = Array.from(document.querySelectorAll(".item")),
mid = ~~(els.length/2);
els.forEach((e,i) => i < mid ? e.classList.add("left")
: i === mid ? e.classList.add("center")
: e.classList.add("right"));
.left {color: red}
.center {color: green}
.right {color: blue}
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
Without jQuery:
(You haven't responded on how to handle even numbered lists. I've chosen to omit the center class and divide in to two parts: before and after mid)
var setMidClasses = function (elementList, beforeMid, atMid, afterMid) {
var i = 0,
hasMid = elementList.length % 2,
mid = Math.floor(elementList.length / 2);
while (i < mid) {
elementList[i].classList.add(beforeMid);
i += 1;
}
if (hasMid) {
elementList[i].classList.add(atMid);
i += 1;
}
while (i < elementList.length) {
elementList[i].classList.add(afterMid);
i += 1;
}
};
setMidClasses(document.querySelectorAll(".item"),
"top", "mid", "bottom");
.top { background-color: green; }
.mid { background-color: orange; }
.bottom { background-color: yellow; }
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div> <!--the middle one-->
<div class="item">4</div>
<div class="item">5</div>
Here's a version that uses recursion with .first()/.last()
probably not very efficient and could be done with a loop, but I wanted to show a version with recursion.
function fixthem()
{
var divs = $("div:not(.right):not(.left)");
// Handle evens, either 2 in the middle:
if (divs.length <= 2) return;
// or none in the middle
if (divs.length <= 1) return;
divs.first().addClass("left");
divs.last().addClass("right");
fixthem();
}
fixthem();
Here's the same without recursion and only a single jquery find at the start (ie hugely more efficient):
function fixthem()
{
var divs = $("div");
// Use 1 for 1 or none in the middle (when even), 2 for 1(odd) or 2(even)
while (divs.length > 2)
{
divs = divs.filter(":not(.right):not(.left)");
if (divs.length <= 2) break;
divs.first().addClass("left");
divs.last().addClass("right");
}
}
fixthem();
To add the class to the middle, run this after the function/at the end of the while loop:
$("div:not(.right):not(.left)").addClass("center")
Working fiddle: https://jsfiddle.net/5huLjh5q/
With center: https://jsfiddle.net/5huLjh5q/1/
var divs = $("div");
// Use 1 for 1 or none in the middle (when even), 2 for 1(odd) or 2(even)
while (divs.length > 2)
{
divs = divs.filter(":not(.right):not(.left)");
if (divs.length <= 2) break;
divs.first().addClass("left");
divs.last().addClass("right");
}
divs.addClass("center");
div { display:inline; border:1px solid black; padding: 1em; margin-top:0.5em }
.left { border:1px solid red;}
.right { border:1px solid green;}
.center { background: pink; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>