Vanya goes to grandmother or dynamic JavaScript adaptive

While working on the adaptive, we now and then have to change the appearance of objects. Usually it’s enough for us to register several media queries and we can do it. In some cases, we need to change the order of the elements, and here, for example, the order property from Flexbox or Grid technology comes to the rescue. But there are times when this does not help us.

The idea of ​​dynamic adaptive is designed to simplify the life of a layout designer and quickly adapt complex layout elements.


For example, look at this case. Here we have the following structure: heading and some text.



But with a certain permission, the designer indicated that the text that was under the heading should now be in a completely different place in the structure.



Typically, layout designers solved this problem in one of two ways. I’ll quickly remind you of them.

I will show it as an example of a life situation when the city boy Vanya really wanted to come to his grandmother for the summer. But not because he missed his grandmother, but because he wanted to go to the river with the girl Olya. And he wanted to do this at a resolution below 992px.



How will we solve the problem of Vani?
We can do the following: Hide Vanya in the city at this permission, and show Vanya on the river with Olya. But for this we need to make a clone of Vani.
Here is the city (original) Vanya. But Vanya, in fact, on the river already with Olya.



But initially this river Vanya is hidden in CSS. We write a media query with the parameters we need, in which we show the new Vanya and hide the old Vanya (city).



Everything worked out for us - at the permission we need, Vanya finds himself under Olya.



Accordingly, when the resolution is greater than 992px, Vanya immediately returns to the city.



And everything seems to be okay, Vanya with Olya when it is needed. You can disperse ...

But not so simple. In fact, some customers are very opposed to duplicate content. This is caused by some problems with SEO, and, in principle, the creation of unnecessary blocks.

Now we are lucky, in our situation Vanya is kind of small, and Vanya happens to be very large, massive, pumped up guys, which respectively require a lot of lines of code. And in this situation, including customers, they ask you to move the DOM object using JavaScript.

This can be done approximately as follows. To begin with, I have to remove Vanya, who is a clone, in HTML and leave Vanya original. In CSS, comment out all the lines that we originally had for another wrapping option. And add here such a simple JS code.

// 
const parent_original = document.querySelector('.content__blocks_city');
const parent = document.querySelector('.content__column_river');
const item = document.querySelector('.content__block_item');

//   
window.addEventListener('resize', move);

//
function move(){
	const viewport_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
	if (viewport_width <= 992) {
		if (!item.classList.contains('done')) {
			parent.insertBefore(item, parent.children[2]);
			item.classList.add('done');
		}
	} else {
		if (item.classList.contains('done')) {
			parent_original.insertBefore(item, parent_original.children[2]);
			item.classList.remove('done');
		}
	}
}

// 
move();

First, we declare the variables where we “dial” to the city, river and original Vani. Next, we begin to “listen” to the width of the screen in order to catch the moment when we need to move Vanya. After we get the width and set the condition in which we check that the width is less than or equal to 992px. When moving Vanya, we assign a class to him, so we check for the presence of a class so that this movement does not happen all the time (Vanya moved once and that’s it, you don’t need to constantly transfer it). And we perform the transfer itself inside the parent (to the river), here we indicate the position in the DOM tree where we need to move Vanya (under Olya, above Olya). And when we have a resolution greater than 992px, we must return Vanya to the place.

We try and see that our Vanya again moved under Olya.

And everything is OK: we don’t have any duplicate content, everything works well, the customer is satisfied, everyone is happy. But this method has a number of problems that are associated with the writing of js-code itself.
We understand that for a single case this is normal - we wrote well. But I got the idea to automate this process. Suppose I need to throw 5-6 completely different elements on the page. For this task, writing a js code using this method is quite laborious and time consuming. And the second point, which, naturally, has strained many of you, is the load on the user's PC, because here we are constantly “listening” to the width of the screen. I also wanted to optimize this moment. It can be optimized in this code as well, but since I wrote an automation tool that I called “dynamic adaptive” , I optimized there.

Now I will show you how it all works.

Having connected my dynamic adaptive, I only need to work with the HTML file and there is no need to access JavaScript. To do this, I need to write the data-da attribute (da is short for dynamic adaptive) and specify three parameters in quotation marks, separated by commas.
The first parameter is where. I take a unique class of the object where I want to move Vanya.
The second parameter is which (in a row). My object has two elements: zero and the first (we know that in JavaScript everything starts from scratch). I need to insert Vanya after the “1” object, so I am writing the “2” parameter.

The third parameter is when. In the third parameter, I write “992”, this is the resolution below which Vanya really wants to get to Olya.

The second and third parameters are optional. By default, the object will be moved to the end of the block at a resolution below 768px.



We check. Everything works the same as in previous versions, but without content duplication and everything is automated.

Consider amenities.

Now, in order to move Vanya over Olya, I just need to change the serial number in the second attribute parameter. And Vanya will already be over Olya.



Convenient, right? I can do the same with the third breakpoint parameter. I can specify, for example, 1280px. (data-da = "content__column_river, 1,1280"). So Vanya will go to Olya a little earlier, precisely when this value is reached. And the coolest thing is that Vanya will return to his place absolutely automatically.

For added comfort, instead of the serial number in the second parameter of the attribute, you can specify one of two values ​​- first or last. When I write first, Vanya will move over the heading "I am a river." It will be located the very first inside the object where we send it. Accordingly, if you specify last, then our Vanya will be at the very bottom of the object.



Let's look at a couple more examples. Suppose we can send Kolya to the lota’s garden, and send Anya to the river to Van and Ola too, it will be more fun. We write for Kolya an attribute with parameters
data-da = "content__column_garden, 2,992" . And for Ani we write data-da = “content__column_river, 1.1280”

We look: Anya, Olya and Vanya on the river. Kolya goes to Lota a little later. Upon return, all the guys are in their places. Everything is great.

Play around in CodePen: codepen.io/FreelancerLifeStyle/project/full/ZmWOPm

Of course, you may have a question: what about the event that could be hung on relocatable objects? For example, initially, when clicking on Anya, we get a window with the inscription: "Everything is OK." Let's see what happens when she moves with the guys to the river.
Move, make a click - everything works fine, the window is also displayed. The event remains with the object when it is moved.



And the second exciting question is the load, which I tried to minimize. Now I will show this by telling a little about the code that is hidden under the hood of the dynamic adaptive.

First of all, I declare some variables, including creating arrays, which I fill out below. I get the attribute, separate it, turning it into an array to get individual elements. Next, I fill out the original position of the objects so that then they can be returned to their parents' place (in the city). After that, I fill in the array of the elements themselves, so that I can then sort it for correct operation.

let originalPositions = [];
let daElements = document.querySelectorAll('[data-da]');
let daElementsArray = [];
let daMatchMedia = [];
// 
if (daElements.length > 0) {
	let number = 0;
	for (let index = 0; index < daElements.length; index++) {
		const daElement = daElements[index];
		const daMove = daElement.getAttribute('data-da');
		if (daMove != '') {
			const daArray = daMove.split(',');
			const daPlace = daArray[1] ? daArray[1].trim() : 'last';
			const daBreakpoint = daArray[2] ? daArray[2].trim() : '767';
			const daDestination = document.querySelector('.' + daArray[0].trim())
			if (daArray.length > 0 && daDestination) {
				daElement.setAttribute('data-da-index', number);
				//   
				originalPositions[number] = {
					"parent": daElement.parentNode,
					"index": indexInParent(daElement)
				};
				//   
				daElementsArray[number] = {
					"element": daElement,
					"destination": document.querySelector('.' + daArray[0].trim()),
					"place": daPlace,
					"breakpoint": daBreakpoint
				}
				number++;
			}
		}
	}
	dynamicAdaptSort(daElementsArray);

	//    
	for (let index = 0; index < daElementsArray.length; index++) {
		const el = daElementsArray[index];
		const daBreakpoint = el.breakpoint;
		const daType = "max"; // MobileFirst   min

		daMatchMedia.push(window.matchMedia("(" + daType + "-width: " + daBreakpoint + "px)"));
		daMatchMedia[index].addListener(dynamicAdapt);
	}
}

And in the end, the optimization that I talked about is implemented: instead of window resize (listen to every window change), we will only listen to changes at the breakpoint using window matchMedia. This way we get the browser response only at some points: those that we indicated in the attributes of our objects.

Next, I collect an array of these breakpoints and hang an event for each breakpoint. In the main dynamic_adapt function, I expand all this inside the array that we collected and sorted, check for the presence of a breakpoint, move the elements, or return them to their place.

// 
function dynamicAdapt(e) {
	for (let index = 0; index < daElementsArray.length; index++) {
		const el = daElementsArray[index];
		const daElement = el.element;
		const daDestination = el.destination;
		const daPlace = el.place;
		const daBreakpoint = el.breakpoint;
		const daClassname = "_dynamic_adapt_" + daBreakpoint;

		if (daMatchMedia[index].matches) {
			// 
			if (!daElement.classList.contains(daClassname)) {
				let actualIndex = indexOfElements(daDestination)[daPlace];
				if (daPlace === 'first') {
					actualIndex = indexOfElements(daDestination)[0];
				} else if (daPlace === 'last') {
					actualIndex = indexOfElements(daDestination)[indexOfElements(daDestination).length];
				}
				daDestination.insertBefore(daElement, daDestination.children[actualIndex]);
				daElement.classList.add(daClassname);
			}
		} else {
			//  
			if (daElement.classList.contains(daClassname)) {
				dynamicAdaptBack(daElement);
				daElement.classList.remove(daClassname);
			}
		}
	}
	customAdapt();
}

Return to home position function:

//   
function dynamicAdaptBack(el) {
	const daIndex = el.getAttribute('data-da-index');
	const originalPlace = originalPositions[daIndex];
	const parentPlace = originalPlace['parent'];
	const indexPlace = originalPlace['index'];
	const actualIndex = indexOfElements(parentPlace, true)[indexPlace];
	parentPlace.insertBefore(el, parentPlace.children[actualIndex]);
}

Naturally, many have a question about Mobile First. There are no problems - we need to change only some variables in the code (removing this setting in HTML is already working):

  1. When I collect an array of breakpoints, we need to change "max" to "min" here. And the event will be “listened” not by max-width, but by min-width.
  2. You also need to change the sorting so that the objects in the array are aligned in a different order when we sort them by breakpoint.

	// 
	function dynamicAdaptSort(arr) {
		arr.sort(function (a, b) {
			if (a.breakpoint > b.breakpoint) { return -1 } else { return 1 } // MobileFirst 
		});
		arr.sort(function (a, b) {
			if (a.place > b.place) { return 1 } else { return -1 }
		});
	}

Also in dynamic adaptive, I use a certain method of obtaining the index when the object is thrown. And here are the first flaws - I have to exclude the transferred object. Those. when I transfer objects to the same place, so that everything is adequate and the objects line up in the order we want, when transferring, I need to exclude the previously transferred object to the same point.

//       
function indexOfElements(parent, back) {
	const children = parent.children;
	const childrenArray = [];
	for (let i = 0; i < children.length; i++) {
		const childrenElement = children[i];
		if (back) {
			childrenArray.push(i);
		} else {
			//  
			if (childrenElement.getAttribute('data-da') == null) {
				childrenArray.push(i);
			}
		}
	}
	return childrenArray;
}

But entails another sorting problem. For example, if I transfer the guys at the same time to the same position, I will not be able to control their order.

One of the ways to develop this dynamic adaptive can be, for example, the creation of several breakpoints. Example, we need to move Vanya to Ola at 1280px, and at 992px Vanya to move to the garden, or return to the place. This functionality is not available now, so there is still work to do. In general, to cover 95% of everyday tasks in the adaptive, the functionality that is now is enough for me. To solve the problem of moving items to another place, I need to spend now a few seconds writing an attribute. And I will get the result I need.

Naturally, I urge absolutely everyone who liked the dynamic adaptive to take part in its testing, development and improvement of both the functional and the optimization of the code itself. And all so that our Kohl, Ani, Vani and Olya would be together when they want it.

Repository on GitHub - github.com/FreelancerLifeStyle/dynamic_adapt

Based on the video " Dynamic Adaptive // ​​Fast Adaptation of Complex Objects in JavaScript " on the youtube channel " Freelancer for Life "

All Articles