'CSS transitions with pure Javascript
I'm doing a mobile page/ad with 4-5 little info boxes on top of a picture. When touched a box will expand and show text regarding that part of the image. When the next box is touched it will expand and the other currently expanded box will return to default. I need to keep it very light weight so I've been playing with a CSS-only version using the :target pseudo class. It works very well apart from the expected page jump to top.
<body>
<div class="page">
<a id="button1" href="#button1">
btn1
</a>
<a id="button2" href="#button2">
btn2
</a>
<a id="button3" href="#button3">
btn3
</a>
<a id="button4" href="#button4">
btn3
</a>
</div>
</body>
To avoid undesired page jump I have concluded that I need to control it with click-events using Javascript. Although it's quite small jQuery lib will eat up my available kB so is not an option. I've spent a few days now browsing the net for solutions to implement some sort of remove/add class function but to no avail. I manage to expand and close the boxes separately but it leaves a cluttered mess when all boxes are open at the same time. I realize I somehow must get it to remove any current instances of the expanded class, but how?
<body>
<div class="page">
<div class="box1">
</div>
<div class="box2">
</div>
</div>
</body>
js:
var hasClass = function (elem, className) {
return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' ');
}
var addClass = function (elem, className) {
if (!hasClass(elem, className)) {
elem.className += ' ' + className;
}
}
var removeClass = function (elem, className) {
var newClass = ' ' + elem.className.replace( /[\t\r\n]/g, ' ') + ' ';
if (hasClass(elem, className)) {
while (newClass.indexOf(' ' + className + ' ') >= 0 ) {
newClass = newClass.replace(' ' + className + ' ', ' ');
}
elem.className = newClass.replace(/^\s+|\s+$/g, '');
}
}
// Boxes Functions
if ( 'querySelector' in document && 'addEventListener' in window ) {
var box1items = document.querySelectorAll('.box1');
var box2items = document.querySelectorAll('.box2');
[].forEach.call(box1items, function (box1) {
box1.addEventListener('click', function(e) {
if ( hasClass(box1, 'box1stage2') ) {
removeClass(box1, 'box1stage2');
}
else {
addClass(box1, 'box1stage2');
}
}
)
});
[].forEach.call(box2items, function (box2) {
box2.addEventListener('click', function(e) {
if ( hasClass(box2, 'box2stage2') ) {
removeClass(box2, 'box2stage2');
}
else {
addClass(box2, 'box2stage2');
}
}
)
});
}
Any help with this is much appreciated! http://jsfiddle.net/negativebyte/BE4BJ/1/
Solution 1:[1]
I had a play around with the last fiddle in your post, combining it and some of my boilerplate code. It will allow an arbitrary number of boxes of any particular class and will close any boxes with the same class when opening a new one.
I do note that your css is identical for box1stage2 and box2stage2 - unless you wish to be able to target items specifically, you could use a single boxStage2 class - however, this would remove the ability to control which boxes belong to groups and ultimately it would affect the number of simultaneously open boxes. In my demo, you can have 1 from each box1 and box2 open at a time. An attempt to open a 3rd box will close the one with the same class thats already open. Also, be aware that I've commented-out the absolute positioning for the sake of my convenience - I didn't feel like making more css rules to target and position the extra boxes. I also chucked-out some of your code, finding it overly verbose and unclear to read. Note particularly my use of the function forEachNode - which basically gives you forEach functionality on nodeLists - rather than just arrays. Perhaps this is personal preference, perhaps it has more value than that, you'd have to ask others.
Code:
<!DOCTYPE html>
<html>
<head>
<script>
/////////////////////////////////////////////////////////////
//// U n u s e d i n t h i s s a m p l e
/////////////////////////////////////////////////////////////
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
function newTxt(txt){return document.createTextNode(txt);}
/////////////////////////////////////////////////////////////
//// R e q u i r e d b y t h i s s a m p l e
/////////////////////////////////////////////////////////////
function toggleClass(element, newStr)
{
var index=element.className.indexOf(newStr);
if ( index == -1)
element.className += ' '+newStr;
else
{
if (index != 0)
newStr = ' '+newStr;
element.className = element.className.replace(newStr, '');
}
}
function forEachNode(nodeList, func)
{
var i, n = nodeList.length;
for (i=0; i<n; i++)
{
func(nodeList[i], i, nodeList);
}
}
window.addEventListener('load', mInit, false);
function mInit()
{
// Boxes Functions
if ( 'querySelector' in document && 'addEventListener' in window )
{
var box1Items = document.querySelectorAll('.box1');
var box2Items = document.querySelectorAll('.box2');
forEachNode(box1Items, addBox1EvtListeners);
forEachNode(box2Items, addBox2EvtListeners);
}
function addBox1EvtListeners(elem, index, list)
{
elem.addEventListener('click', onBox1_click, false);
}
function addBox2EvtListeners(elem, index, list)
{
elem.addEventListener('click', onBox2_click, false);
}
}
var hasClass = function (elem, className) {
return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' ');
}
var addClass = function (elem, className) {
if (!hasClass(elem, className)) {
elem.className += ' ' + className;
}
}
var removeClass = function (elem, className) {
var newClass = ' ' + elem.className.replace( /[\t\r\n]/g, ' ') + ' ';
if (hasClass(elem, className)) {
while (newClass.indexOf(' ' + className + ' ') >= 0 ) {
newClass = newClass.replace(' ' + className + ' ', ' ');
}
elem.className = newClass.replace(/^\s+|\s+$/g, '');
}
}
function onBox1_click(e)
{
var box1Elems = document.getElementsByClassName('box1');
var clickedBox = this;
forEachNode(box1Elems, remClass1);
toggleClass(this, 'box1stage2');
function remClass1(elem, index, nodeList)
{
if (elem != clickedBox)
removeClass(elem, 'box1stage2');
}
}
function onBox2_click(e)
{
var box2Elems = document.getElementsByClassName('box2');
var clickedBox = this;
forEachNode(box2Elems, remClass2);
toggleClass(this, 'box2stage2');
function remClass2(elem, index, nodeList)
{
if (elem != clickedBox)
removeClass(elem, 'box2stage2');
}
}
</script>
<style>
.page {
position:relative;
top:0px;
left:0px;
width:580px;
height:500px;
background:#CCCCCC;
}
.box1 {
/*
position:absolute;
top:250px;
left:85px;
*/
width:40px;
height:40px;
border-radius:5px;
background:rgba(0,0,0,0.5);
-webkit-transition: All 0.4s ease;
}
.box1stage2 {
width:200px;
height:100px;
background:rgba(0,0,0,0.85);
-webkit-transition: All 0.4s ease;
}
.box2 {
/*
position:absolute;
top:200px;
left:230px;
*/
width:40px;
height:40px;
border-radius:5px;
background:rgba(0,0,0,0.5);
-webkit-transition: All 0.4s ease;
}
.box2stage2 {
width:200px;
height:100px;
background:rgba(0,0,0,0.85);
-webkit-transition: All 0.4s ease;
}
</style>
</head>
<body>
<div class="page">
<div class="box1">
</div>
<div class="box2">
</div>
<div class="box1">
</div>
<div class="box2">
</div>
</div>
</body>
</html>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | enhzflep |
