Следим за фигурой не отрываясь от компьютера

от автора

С вашего позволения, публикую топик от имени моего безвременно read-only друга и коллеги Terion. Возможно, мы промахнулись с хабом – сообщите нам об этом.

Нет, речь в посте пойдет не о гипножабе, которая через монитор закодирует вас на похудение, или еще о чем-то таком. Речь пойдет о счетчике потраченных за компьютером калорий. Точнее, потраченных на сайтах. А также о некоторых tips-n-tricks с анимацией.

Мы давно хотели сделать нечто подобное, а тут подвернулась возможность и мы с радостью принялись за дело. И получился тренажерный зал онлайн LazyS. Конечно, никто не претендует на абсолютную точность и, безусловно, это фановая игрушка, но цифры взяты и не с потолка. К тому же, там довольно забавные тренажеры.

Как это работает


Человек в среднем в сутки тратит 1200 ккал. Это показатель не для спортсменов или работников физического труда, а как раз для человека среднестатистического (работающего в офисе). На активную фазу суток (т.е. на бодрствование), в качестве расчетной точки, мы выделили 1000 ккал.

С ростом пульса — увеличивается и расход энергии. Соответственно, дальше считать довольно просто: замеряем скорость движения, не влияющую на пульс, берем её за опорную, приводим коэффициент для бОльших скоростей, замеряем время и умножаем на средний расход калорий за это время.

Конечно, это все очень приблизительно, но для поверхностной оценки — вполне пойдет.

Где это работает

Во-первых, на самом сайте есть забавные тренажеры. Во-вторых, есть букмарклет, который лучше всего работает на ajax-driven сайтах (в частности, отлично получается на vk.com). Заходите и смотрите, сколько тратите энергии на движения мышкой).

Немного о коде

Расчет дистанции, пройденной курсором — достаточно тривиальная задача (привет, теорема Пифагора). А вот о чем было бы интересно рассказать, так это о реализации гири (или «офисных вертолетов»), которая, на мой взгляд, получилась очень интересной.

Гиря должна вращаться по кругу, захваченная мышкой. Конечно, она должна удерживаться, когда мышка покидает гирю. А еще нам нужно считать угловую скорость и рассчитывать тень под ней.

Аниматор jQuery по координатам гири тут не поможет, пришлось искать другое решение. И оно нашлось довольно интересное: в качестве основы для вращения гири мы будем использовать отдельный элемент (#center) и манипулировать его margin-bottom.

При захвате гири мышкой — начинаем фиксировать угол, образующийся между курсором и центром круга и записываем его значение в margin-bottom элемента #center.

var a = centerPos.left - e.pageX; var b = centerPos.top - e.pageY; var c = Math.sqrt( Math.pow(a,2)+Math.pow(b,2) ); var angle = Math.asin(b/c) * -1; 

Одновременно с этим рассчитываем положение гири, исходя из полученного угла (простым прямоугольным треугольником, где радиус трэка — его гипотенуза):

var a2 = R * Math.cos(angle); var b2 = R * Math.sin(angle); girja.css({'top': b2, 'left': a2}); // гиря позиционирована абсолютно относительно контейнера с position:relative, поэтому компенсировать ничего не нужно 

При отрыве мышки, или когда её плечо относительно центра становится менее 40 точек (чтобы нельзя было крутить вокруг центра вплотную и накручивать счетчик) в действие вступает аниматор jQuery, который анимирует margin-bottom элемента #center до значения pi/2 (в радианах). Вся соль в двух вещах:

  • easeOutElastic easing, дающий красивый инерционный возврат
  • step-функция аниматора.

Степ-функция как раз и анимирует саму гирю. На каждом кадре анимации мы получаем текущий угол и по нему выставляем координаты гири:

step: function(now, fx){ 	var a2 = R * Math.cos(now); 	var b2 = R * Math.sin(now); 	if (aUp < 0) { a2 = a2 * -1 }; 	girja.css({'top': b2, 'left': a2}); } 

Отдельно про тень: ее хотелось сделать живой и красивой. После того, что вышло с расчетом координат гири, отрисовать тень должным образом не составило труда. Но есть одна хитрость:

shad.css({'left': a2, 'opacity': Math.pow(b2 / R,15)}); 

Горизонтальное смещение тени равно смещению гири. А вот прозрачность считается делением вертикального сдвига гири на радиус трека, где оба члена возведены в 15ю степень.

Зачем такое приведение? Во-первых, степень должна быть нечетной, чтобы сохранить знак (когда гиря поднимается выше центра — смещение становится отрицательным и прозрачность не станет нарастать). Во-вторых степени «регулируют скорость приближения к границе». В кавычках, потому что описание совсем не математическое, но как это правильнее сказать — не знаю.

Когда b2 и R равны, деление, независимо от степени, даст 1. А вот по мере уменьшения b2 (горизонтального сдвига гири), чем выше степень обоих членов — тем быстрее результат деления приблизится к 0 и тень пропадет.

Из этого всего в итоге получился такой код:

$('#karusel-machine').each(function(){ 		var girja = $('#girja'); 		var shad = $('#shadow'); 		var center = $('#center'); 		var angle = 0; 		var R = 193; 		var win = $(window); 		var bottomRad = (Math.PI / 2).toFixed(3); 		 		girja.on('mousedown', function(e){ 			center.stop(true, false); 			win.off('mouseup.girja'); 			e.preventDefault(); 			 			var centerPos = {left:center.offset().left, top: center.offset().top}; 			 			var baseAngle = parseFloat(center.css('margin-bottom')); 			var startAngle = baseAngle; 			var moveStart = new Date().getTime(); 			var moveEnd = 0; 			 	 			win.on('mousemove.girja', function(e){ 				var a = centerPos.left - e.pageX; 				var b = centerPos.top - e.pageY; 				var c = Math.sqrt( Math.pow(a,2)+Math.pow(b,2) ); 				 				if ( c < 40 ) { 					win.trigger('mouseup.girja', {pageX:e.pageX, pageY: e.pageY}); 				} 				 				var angle = Math.asin(b/c) * -1; 				var delta = Math.abs(angle - startAngle); 				moveEnd = new Date().getTime(); 				var t = moveEnd-moveStart; 				var V = delta/t; 				 				var k = V/0.005; 				if (k<1) k=1; 				var spentCals = calsPerMs*k*t; 				 				if (typeof(spentCals) == "number" && spentCals > 0) { 					window.calsSpent += spentCals; 					var kilocals = window.calsSpent / 1000; 					calsSpantInformer.text(kilocals.toFixed(2)); 				} 				 				center.css({'margin-bottom':angle}); 				 				startAngle = angle; 				moveStart = moveEnd; 				 				var a2 = R * Math.cos(angle); 				var b2 = R * Math.sin(angle); 				 				if (a > 0) { 					a2 = a2 * -1; 				} 				 				girja.css({'top': b2, 'left': a2}); 				shad.css({'left': a2, 'opacity': Math.pow((b2/R),15)}); 			}); 			 			win.on('mouseup.girja', function(e){ 				win.off('mouseup.girja'); 				var aUp = parseFloat(girja.css('left')); 				var bUp = parseFloat(girja.css('top')); 				 				win.off('mousemove.girja'); 				center.animate( 					{'margin-bottom': bottomRad }, 					{ 						duration: bUp>0?2500:2000, 						easing: 'easeOutElastic', 						complete: function(){ 							center.stop(true, true).css({'margin-bottom': bottomRad }); 							win.off('mouseup.girja'); 						}, 						step: function(now, fx){ 							var a2 = R * Math.cos(now); 							var b2 = R * Math.sin(now); 							if (aUp < 0) { a2 = a2 * -1 }; 							girja.css({'top': b2, 'left': a2}); 							shad.css({'left': a2, 'opacity': Math.pow((b2/R),15)}); 						}, 						queue: false 					} 				) 			}); 			 		}); 		 	}); 

Такими, достаточно нехитрыми способами, мы получили красивую и довольно живую анимацию для этого тренажера.

ссылка на оригинал статьи http://habrahabr.ru/post/155731/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *