Статьи

Как сохранить ваш DOM от смещения вокруг

Я хотел написать это некоторое время, но до сегодняшнего дня я так и не дошел до этого, когда встреча внезапно была отменена. Было это или встать на беговую дорожку, и, к сожалению, беговая дорожка пропала. В последнее время я заметил общую проблему как с веб-приложениями, так и с нативными. Проблема заключается в следующем: приложение отображает какой-то динамический контент. В этом контенте находятся различные элементы пользовательского интерфейса, на которые вы можете нажать. В то же время приложение извлекает дополнительный контент асинхронно. Когда этот контент входит, он отображается тогда, и макет контента корректируется по мере поступления нового материала. Проблема в том, что пользователь, возможно, только собирался нажать на кнопку, ссылку или что-то еще, и теперь находит что их действие щелчка ничего не сделало. Или хуже — активировал другойдействие, которое они не хотели. TweetDeck особенно плохо об этом. Facebook, на удивление, на самом деле чертовски хорош в этом. Давайте рассмотрим простой пример на случай, если у меня нет смысла.

Я построил простое приложение, которое позволяет вам просматривать и, как, изображения кошек. Позвольте мне прояснить, это всего лишь доказательство концепции, но если кто-то создаст этот сайт, я буду делать это каждые пять минут или около того. Когда приложение загружается, вы получаете изображение кошки и некоторые, но не все, пользовательского интерфейса.

При наведении курсора мыши на кнопку «Мне нравится» пользовательский интерфейс внезапно обновляется, отображая статистику об изображении. Первоначальный разработчик подумал, что было бы здорово загрузить это после остальной части страницы. Не очень плохая идея, верно? Если основной задачей является показ изображений кошек, тогда загрузка статистики имеет смысл. Но посмотрите, как меняется DOM после загрузки статистики:

Как видите, кнопка «Мне нравится» была сдвинута вниз. В этом случае худшее, что вы получаете, — это событие щелчка, которое ничего не вызывало, но все равно раздражает. Вы можете сами продемонстрировать это здесь: http://www.raymondcamden.com/demos/2014/aug/5/test1.html . Давайте быстро посмотрим на код, чтобы вы могли увидеть, как была создана оригинальная версия. Во-первых, HTML.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<title></title>
		<meta name="description" content="">
		<meta name="viewport" content="width=device-width">
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
		<script src="app1.js"></script>
	</head>
	<body>

		<div id="content">
			<img src="http://placekitten.com/300/300">
			<div id="stats"></div>
			<button>Like!</button>
		</div>
		
	</body>
</html>

And here is the JavaScript. I used a simple setTimeout to fake a slow AJAX request.

$(document).ready(function() {

	//fake a delayed update
	window.setTimeout(function() {
		$("#stats").html("<b>Likes:</b> 912");
	},2000);
	
});

Ok, so how can we fix this? One approach may be to simply specify a set height for the DOM item we are updating. That way there won’t be a «shift» when the content is uploaded. For example:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<title></title>
		<meta name="description" content="">
		<meta name="viewport" content="width=device-width">
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
		<script src="app2.js"></script>
		<style>
			#stats {
				height: 30px;
				background-color: #c0c0c0;
			}
		</style>
	</head>
	<body>

		<div id="content">
			<img src="http://placekitten.com/300/300">
			<div id="stats"></div>
			<button>Like!</button>
		</div>
		
	</body>
</html>

Notice I added both a height and a background-color. The color change was simply to ensure that my height was working right. It also gives the user a bit of a clue that something is going to be there. (I won’t pretend this is pretty, but hopefully you get the idea.) You can try this version here: http://www.raymondcamden.com/demos/2014/aug/5/test2.html.

But we can do even better, right? I don’t like the big empty box. Let’s modify the stats area to include the labels for our stats (well, our stat), so that the update is a bit less jarring. While we’re at it, our image service (in this case, the epic placekitten.com) can also be a source of DOM shifting as the image loads. I should have added specific height and width to the image.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<title></title>
		<meta name="description" content="">
		<meta name="viewport" content="width=device-width">
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
		<script src="app3.js"></script>
		<style>
			#stats {
				height: 30px;
			}
		</style>
	</head>
	<body>

		<div id="content">
			<img src="http://placekitten.com/300/300" width="300" height="300">
			<div id="stats">Likes: <span id="likes"></span></div>
			<button>Like!</button>
		</div>
		
	</body>
</html>

I modified the JavaScript now to both add a loading message and to just change the span.

$(document).ready(function() {

	$("#likes").html("<i>Fetching</i>");

	//fake a delayed update
	window.setTimeout(function() {
		$("#likes").html("912");
	},2000);
	
});

You can run this version here: http://www.raymondcamden.com/demos/2014/aug/5/test3.html.

This isn’t rocket science, but as I said in the beginning, I find myself surprised by how many sites and apps seem to have this problem. Keep it in mind when working on your next project.