რატომ PHP?

threads

ეს მორიგი სტანდარტული პოსტი არ არის ენების / პლატფორმების დაპირისპირებაზე. უამრავი ნეგატიური სტატიის და მოსაზრების მიუხედავად წლებია რაც php-ის ჩემს გულში განსაკუთრებული ადგილი უჭირავს და სულ ვცდილობდი დამედგინა რატომ. ბოლოს სიყვარულის სიბრმავეს დავაბრალე და ტკბილ მოგონებებს, რაც საყვარელ ხალხთან და პროექტებთან მაკავშირებდა, მაგრამ ახლა მე მგონი კიდევ ვიპოვე ერთი დიდი მიზეზი.

მოდი ცოტა შორიდან დავიწყებ.
ერთხელაც, .net რომ ვითარდებოდა ვებში, შემოვიდა asp ვებ ფორმები და პოპულარული გახდა. რამდენჯერ ვცადე შიგნით გარკვევა, რა წიგნები არ წავიკითხე, მაგრამ არ მიმივიდა გული არცერთხელ, თუმცა მიზეზს ვერ ვასახელებდი მაშინ. ეგ მიზეზი იყო თავსმოხვეული ხელოვნურობა. გეუბნებოდა ფრეიმვორკი, რომ მე ვიცი რასაც ვაკეთებ, შენ მარტო ეს ღილაკი დასვი და იმას დააჭირე. მერე მიდიოდა და ზურგს უკან რას აკეთებდა?! 😀 ამხელა სტეიტს დაათრევდა აქეთ იქით რიქვესთებს შორის. მე მგონი პლატფორმა კი არა, ერთი დიდი workaround იყო ვებისთვის.

ვერ ვიტან როცა ფარდის უკან აკეთებენ ყველაფერს. მოხდება შეცდომა და კაცმა არ იცის ვისი და რისი გამოწვეულია სინამდვილეში.

ახლა ჯავა 😀 ჯავა მიყვარს. მაგრამ ვებში ვერ შევეგუე ჯავას. მე მგონი რეალურად ახლა გავიაზრე თუ რატომ ვიყენებთ მისთვის Application სერვერს და არა ვებ სერვერს. უხეში ანალოგია რომ გავაკეთო, წარმოიდგინეთ რომ როგორც დესკტოპისთვის გაუშვებთ რაღაც პროგრამას, ანალოგიურად უშვებთ თქვენს ერთ პროგრამას სერვერზე ყველა მომხმარებლისთვის. ერთს! ყველასთვის! მთელი პარალელიზმი შიგნით არის ფაქტიურად.

Java EE-ში რიქვესთების საპასუხოდ პროგრამისტები ვწერთ სერვლეტ კლასებს, იქ კი instance ცვლადების გაკეთება არ არის კარგი. იცით რატომ? იმიტომ რომ თითო სერვლეტის ერთი ობიექტი გამოიყენება ყველა რიქვესთის საპასუხოდ (მთლად მაგაზე დაყრდნობაც არ შეიძლება, რადგან კონტეინერი აკონტროლებს როდის მოკლას ან ჩაანაცვლოს ის ინსტანსი). სტატიკური რაღაცები, სინგლტონ კლასები – ყველაფერი ჩვეულებრივ არსებობს აპლიკაციაში და shared არის მომხმარებლებს შორის. სპეციალურადაც კი არის ობიექტი – ‘აპლიკაციის კონტექსტი’ რომელიც სერვლეტებს შორის shared მონაცემებს დაატარებს და არაერთ best practice-ში გამოიყენება, თუნდაც რაღაც ობიექტების კეშირებისთვის. ამ ყველაფერს კი სჭირდება სინქრონიზაცია. უნდა იცოდეს პროგრამისტმა რა ბიბლიოთეკები და კლასებია thread-safe, იცოდეს კოდის რა ნაწილი მოხვდება ცალკე ნაკადში და რა ნაწილი იქნება გაზიარებული. მერე შესაბამისად lock-ებით და სინქრონიზაციის სხვადასხვა მექანიზმებით უნდა აკონტროლოს ეს ყველაფერი.

იყო რაღაც ფრეიმვორკები, ახლაც არის. ახლების ნახვის სურვილი აღარც გამჩენია მათ შემდეგ, იმიტომ რომ მე მგონი იმ ვებ ფორმებზე უარესად აფარებდნენ ფარდებს ყველაფერს. მახსოვს Struts რომ მიშრობდა სისხლს. xml-ში აკონფიგურირებდი და მერე ჯადოსნურად უნდა ემუშავა ბევრ რამეს. მაგრამ უმეტეს გაშვებაზე ერთი შეცდომა გამოდიოდა ხოლმე, რაც ჭირივით მეზიზღებოდა 😀 ფაილს ვერ ვპოულობო. ვერ გაიგებდი რა უნდოდა, xml-ში გამოგრჩა წერტილი თუ გადატვირთვა დააკლდა სერვერს. Spring-ზე არც დავიწყებ…

asp.net-ზე როცა იწერება ვებ აპლიკაცია, იქაც ერთი პროგრამა ეშვება. იქაც თუ შექმნით სტატიკურ კლასს, საერთო იქნება და რაღაც საერთო კონტექსტის ობიექტიც ჰქონდათ თუ სწორად მახსოვს. მერე asp MVC ფრეიმვორკი რომ შექმნეს, იქ კარგად ცდილობდნენ ყველა რიქვესთი stateless ყოფილიყო და როცა მომიწია მაგით წერა, ერთი სიამოვნება იყო ჩემთვის რადგან ფაქტიურად ზუსტად ისე ვწერდი როგორც php-ში.

(ცოტა ვურევ ახლა მე ერთმანეთში რაღაც რაღაცებს, მაგრამ ყველაფერზე რომ ვრცლად დავწერო, მთლიანი ბლოგი არ მეყოფა ალბათ).

განა ვაკნინებ ამ პლატფორმებს და ფრეიმვორკებს (ვებ ფორმების გარდა), უამრავი არაადამიანური შრომა არის ჩადებული და მეც წლების მანძილზე გამომიყენებია. მადლობის მეტი რა მეთქმის.
მარტო იმას ვამბობ რომ გაცილებით სასიამოვნო და კომფორტულია ისეთ გარემოში წერა, სადაც ამხელა კომპლექსურობა მოხსნილია და თანაც ისეა მოხსნილი რომ ფარდის უკან გადაძრომა არ გიწევს ხშირად. ჩემთვის ერთ-ერთი ეგეთი გარემოა php.

მეორე გახდა node.js, რომელზეც ქვევით ვიტყვი უფრო დეტალურად.

როცა PHP-ის ვებ სერვერზე ვიყენებთ, უმეტეს შემთხვევაში ისე ვაკონფიგურირებთ რომ ის არ ინახავს არანაირ state-ს. თუ CGI-ის, FastCGI-ის ან Apache-ს Prefork რეჟიმს გამოვიყენებთ, სერვერთან მოსული თითოეული რიქვესთისთვის ახალი პროცესი გაეშვება და მარტო ერთ ნაკადში შესრულდება php-ის კოდი. მაშინაც კი თუ php-ის კომპილირებული ფაილების კეშირებას ვაკეთებთ, სკრიპტის თითოეული ინსტანსი მაინც ცალკე შეიქმნება და ფიზიკური მეხსიერებაც ცალკე გამოეყოფა.
თურმე სახელიც ჰქონია ასეთ მიდგომას – Shared nothing architecture.

მესმის რომ წარმადობის პრობლემა შეექმნება ასე, მაგრამ საშუალო ზომის ვებ აპლიკაციების შემთხვევაში რამდენჯერ მისულხართ მაგ პრობლემამდე?

(ცალკე დეველოპმენტის რეჟიმში კიდევ – არ ვიცი როგორ ახერხებს რომ კოდი სერვერის მხარეს კომპილირდება და მაინც წამიერად მუშაობს. არადა ჯავაზე ან visual studio-ში თუ ვარ, ყველა გაშვებაზე უნდა დაბილდოს-დალინკოს და აშკარად ვამჩნევ მაგას. დაკომპილირებულ ბიბლიოთეკებში ბოდიალი სხვა თემაა კიდევ..)

ჰოდა node.js-ს რაც შეეხება, მანდ საერთოდ მარტო ერთი ნაკადი გვაქვს. მერე უკან, ჩვენს გარეშე კი აქვს რაღაცები გაპარალელებული, მაგრამ პროგრამისტები რასაც ვწერთ, იქ ერთისთვის ვწერთ და სრულიად არ გვანაღვლებს race condition-ები. მაგის მოდელზე ჯობია ცალკე პოსტს დავწერ.
node-ში წერასაც ექნება თავისი ნაკლები და ბევრჯერ წავიმტვრევ ალბათ ცხვირსაც, მაგრამ ნაკადებზე რომ არ ვფიქრობ, ძალიან მომწონს.

ისიც მესმის, რომ ნელ-ნელა გარდაუვალი გახდება წარმადობაზე ფიქრი და წინსვლისთვის მაინც მოგვიწევს ისეთი პროგრამების შექმნა, რომლებიც ბევრი პროცესორისთვის და ბევრი ნაკადისთვის იმუშავებს, მაგრამ რაღაცნაირად იმედი მაქვს რომ იქნება რაღაც იდეები (მაგალითად როგორც node.js ან როგორც nosql ბაზები) და ნაწილობრივ მაინც მოგვაცილებენ shared state-ის თავისტკივილს.

მეტყვით ალბათ, რომ სიზარმაცის და Thread-ების აზრზე არ ყოფნის გამოა ეს ყველაფერი გამოწვეული. მაგის უარყოფას არ დავიწყებ 🙂

ჯავასკრიპტის ხრიკები

ფოტო jstips-დან

jstips-header-blog

ადრე ჯავასკრიპტის ერთი ბიბლიოთეკის გარჩევა მომიწია, რომელიც მინიმიზატორში იყო გატარებული და მაგის აღდგენის პროცესში ის გზები შემხვდა რითიც მინიმიზატორები კოდის მოცულობას ამცირებენ. ყველაზე მეტად ცხადია ცვლადების სახელების ერთ-ორ ასომდე დაყვანა ამცირებს. მაგის გარდა კიდევ იყო რაღაცები, რასაც საღ გონებაზე პროგრამისტი არასდროს გააკეთებს, მაგრამ just for fun 😀 გთავაზობთ რამდენიმეს გამოსაცნობად:
(მერე ისეთებსაც, რასაც აზრი აქვს)

1. როგორ ჩაწერდით false-ს ორი სიმბოლოთი? true-ს?

!1 false, !0 true

2. როგორ შეამცირებდით ამ კოდს?

if (a) b();

ამათ?

if (!a) b();

if (a) {
b = 1;
c = 2;
}

&& და || ჯავასკრიპტშიც short-circuit ოპერატორებია, ანუ გამოსახულების შესრულებისას როგორც კი ცხადი გახდება შედეგი, შემდეგ აღარ გრძელდება დანარჩენი ნაწილების ევალუაცია, ფუნქციების გამოძახება, ა.შ. ამიტო if-ის მაგივრად შეგვიძლია დავწეროთ a&&b().
მეორე შემთხვევაში იქნება a||b().
მესამე შემთხვევაში დიდი არაფერი ხდება, უბრალოდ მინიმიზატორები ხშრად ერთ ბრძანებად აერთიანებენ ყველაფერს და მძიმით გამოყოფენ. მერე ბლოკების გაკეთება აღარ უწევთ.
if (a) b=1, c=2

3. ყველაზე მოკლედ როგორ შეამოწმებთ ასეთ რამეს ცვლადისთვის: თუ ცვლადი არის undefined, 0, false, “”, null ან NaN – ყველა ამ შემთხვევაში დააბრუნოს false, წინააღმდეგ შემთხვევაში true.
(ეს მინიმიზატორში არ იყო, უბრალოდ კოდში ხშირად კარგად გამომდგომია).

bool ტიპად გადაქცევით: result = !!a

4. როგორ შეამცირებდით ამ ნაწილს? ვთქვათ რაღაც arg-ის ვალიდაციას აკეთებთ და თუ არაა განსაზღვრული, default მნიშვნელობას ანიჭებთ. ესეც არ არის მინიმიზატორში.

var options = (arg) ? arg : [];

ჯავასკრიპტში && და || ცოტა არასტანდარტულად იქცევიან – boolean-ს კი არ აბრუნებენ შედეგად, არამედ იმ ოპერანდს, რომელიც გამოსახულების ბედს გადაწყვეტს.
მაგალითად 1 && 0 && false დააბრუნებს 0-ს.

შესაბამისად ის გამოსახულება უფრო ლამაზად შეგვიძლია ასე ჩავწეროთ

var options = arg || [];

4. ამას მინიმიზატორი არ აკეთებს, მაგრამ, ვთქვათ რიცხვის ქვემოთ დამრგვალება გვინდა (ნოლისკენ). როგორი იქნებოდა მაგის ყველაზე მოკლე და სწრაფი ვარიანტი?

მოდი ჯერ პირდაპირ დავწერ. ქვემოთ დამრგვალებისთვის არსებობს ფუნქცია Math.floor(), თუმცა ეს სტანდარტულად ამრგვალებს negative infinity-ისკენ. ანუ 12.6 გადავა 12-ში, მაგრამ -12.6 გადავა -13-ში. ამ კითხვის პასუხში ეგ შემთხვევა არ გაითვალისწინება (და ისედაც მე პირადად უფრო ხშრად ნოლისკენ დამრგვალება უფრო მჭირდება, რომ -12.6 გახდეს -12, რადგან ნიშანი სხვა დატვირთვით მაქვს ხოლმე).
ჰოდა გამოვიდა შემდეგი:

foo > 0 ? Math.floor(foo) : Math.ceil(foo)

თუ არ ვართ დარწუნებული რომ foo რიცხვი იქნება და NaN-ის მიღება არ გვინდა, მაშინ შემოწმებების დამატება მოგვიწევს:

typeof foo === 'number' && !isNaN(foo) && foo !== Infinity
? foo > 0 ? Math.floor(foo) : Math.ceil(foo) : 0;

ჰოდა ახლა ეს ყველაფერი ჩავწეროთ ორი სიმბოლოთი 🙂 foo-ს გარდა ცხადია

ბინარული ოპერაციები დაგვიჭრდება აქ, ორი ვარიანტი გვაქვს – ორმაგი ბინარული უარყოფა ან ბინარული ‘ან’ ნულთან.
~~foo
foo|0

თუ ბინარული ოპერაციის დეტალების ნახვა გინდათ, ცოტა ამ ლინკზე წერია.

5. 1000000, 0.0000023 და სხვა ბევრ ნულიანი რიცხვების დაპატარავება

1e6, 23e-7 – ნულების თვლა ცუდია 😀

საერთო კოდი ბრაუზერსა და node.js აპლკაციაში

წინა პოსტებში ვახსენე რომ ჯავასკრიპტზე მომიწია ანიმაციის ლოგიკის წერა. იგივე კოდი (მათ შორის გეომეტრიაც) ბექშიც მჭირდებოდა, ამიტომ ვიფიქრე – რატომ გადავწერო.. ბექს node.js-ით გავაკეთებ და იგივე კოდს გამოვიყენებ. შესაბამისად თავსებადობის ამოცანა გამიჩნდა.

ცხადია, პირველ რიგში საერთო ბიბლიოთეკებიდან უნდა გამომეყო ბრაუზერთან დაკავშირებული კოდი (DOM-ის მანიპულაცია, ვორკერების ურთიერთობა, ა.შ.). მეორე რიგში node.js-ს ცოტა განსხვავებული მეთოდი აქვს სკრიპტების იმპორტისთვის. თუ ბრაუზერში <script> ტეგს ვიყენებთ, node.js-ში მოდულები უნდა გავაკეთოთ და require მეთოდით ჩავტვირთოთ.

მაგალითად, მარტივი მოდული ასე გამოიყურება (ფაილის სახელია Square.js):

exports.Square = function (side){
	this.side = side;
	this.getArea = function(){
		return side * side;
	}
}

module.exports ან პირდაპირ exports მეთოდს ვიყენებთ იმისთვის რომ ობიექტები გარედან ხელმისაწვდომი გავხადოთ. ამ ფაილში აღწერილი სხვა დანარჩენი ფუნქციები და ცვლადები private იქნება გარე სამყაროსთვის.

მოდულის გამოყენების მაგალითი სხვა ფაილში:

var module = require('./Square');
var square = new module.Square(10);
console.log(square.getArea());

Square.js ფაილი ბრაუზერისთვის თავსებადი რომ გავხადოთ, exports მეთოდს რამე უნდა მოვუხერხოთ.
მაგალითად, შეიძლება შემოწმების ჩამატება:

var Square = function (side){
	this.side = side;
	this.getArea = function(){
		return side * side;
	}
}

if (typeof exports !== 'undefined') {
	exports.Square = Square;
}

აქ კიდევ ერთი მომენტია. როგორც ზევით აღვნიშნეთ, node.js-ში exports-ის გარეთ რაც რჩება, ხელმისაწვდომი არ არის გარედან. ბრაუზერის შემთხვევაში კი Square გლობალური ცვლადი გამოდის. სწორი იქნებოდა, რომ მისი წვდომაც შეგვეზღუდა. მაგალითად ასე:

(function(exports){
	var Square = function (side){
		this.side = side;
		this.getArea = function(){
			return side * side;
		}
	}

	if (typeof exports !== 'undefined') {
		exports.Square = Square;
	}	
})(typeof exports !== 'undefined' ? exports : this['module'] = {});

 

RequireJS + node

არის კიდევ ერთი გზა. ყოველთვის მინდოდა Require.js ბიბლიოთეკის დატესტვა. როგორც აღმოჩნდა მაგას ადაპტერიც აქვს nodejs და მისნაირებისთვის. ასე რომ, ავდექი და Require-ზე გადავაწყე პროექტი. შედეგად ერთნაირი ფორმატით შემიძლია ვწერო მოდულები ფრონტისთვისაც და ბექსითვისაც. თვითონ ბიბლიოთეკაც საკმაოდ მომეწონა. ძალიან მარტივად გადაეწყო ჩემი კოდი და პირველივე გაშვებაზე იმუშავა რაც არ უნდა უცნაური იყოს.

ზევით აღწერილი მოდულის მაგალითი Require.js-ით:

define(function(){
	return function Square(side){
		this.side = side;
		this.getArea = function(){
			return side * side;
		}
	}
});

მოდულის გამოყენების მაგალითი:

requirejs(['Square'], function(Square) {
    var square = new Square(10);
	console.log(square.getArea());
});

JavaScript animation / game loop (ნაწილი 2)

ამ პოსტის წინა ნაწილში requestAnimationFrame მეთოდზე ვისაუბრე, რომლის საშუალებით ბრაუზერის რენდერინგს შეგვიძლია ჩვენი ქოლბექი მივაბათ და შესაბამისად მაგ დროს განვაახლოთ ანიმაციის ან თამაშის მდგომარეობა.

მინდა აღვნიშნო, რომ ამ მეთოდში ჯობია მხოლოდ გადახატვის ლოგიკა იყოს, ხოლო ანიმაციის ან თამაშის ლოგიკის (მდგომარეობების გამოთვლა, ა.შ.) ადგილი იქ ცალსახად არ არის. ამას ბევრი მიზეზი აქვს: ბრაუზერი გვერდის რენდერის დროს სხვადასხვა ფაქტორს ითვალისწინებს – მაგალითად ტაბი აქტიურია თუ არა, ლეპტოპის აკუმულატორი რა დონეზეა, მონიტორი რა სიხშირით ახლდება, ა.შ. შესაბამისად შეიძლება ჩვენი ქოლბექების გამოძახების რაოდენობა შეამციროს ან გაზარდოს დროის კონკრეტულ პერიოდში.

აქედან გამომდინარე, უმჯობესია რომ ანიმაციის ან თამაშის მდგომარეობები ცალკე გამოითვალოს / დაგენერირდეს და requestAnimationFrame-ის ქოლბექში მხოლოდ მათი რენდერის კოდი ჩაიწეროს.

ცალკე გენერაციაც ბევრნაირად შეიძლება. რადგან ეგ მნიშვნელოვნად განსაზღვრავს ჩვენი ანიმაციის წარმადობას, ბევრი ვიფიქრე როგორ გამეკეთებინა და ახლაც არ ვარ დარწუნებული ჩემი კოდის სისწორეში. სხვის მიდგომებს მოვიყვან მაგალითად:

1. შეიძლება setInterval და setTimeout მეთოდებით დაგენერირდეს მდგომარეობები, ხოლო requestAnimationFrame-ის ქოლბექმა აარჩიოს იქიდან შესაბამისი მდგომარეობა და ის დაარენდეროს.

2. პირველი პუნქტის ანალოგიურად მდგომარეობები დროის რაღაც ინტერვალში დაგენერირდეს, თუმცა ეს ლოგიკა ცალკე ნაკადში – მუდმივად გაშვებულ web worker-ში იყოს გატანილი, რომ მთავარი ნაკადი ძალიან არ დაიტვირთოს (ჩემი აზრით ეს უკეთესია). requestAnimationFrame-მა კი worker-დან მიღებული მდგომარეობებიდან შეარჩიოს დროის შესაბამისად და დაარენდეროს გარემო.

3. ეს მეორე პუნქტის მსგავსია, თუმცა setInterval-ის ნაცვლად ინიციატორი requestAnimationFrame-ის ქოლბექი იყოს. მის გამოძახებაში დამატებით იყოს worker-თან შეტყობინების გაგზავნა, რომ ვორკერმა მომდევნო X რაოდენობის მდგომარეობა დააგენერიროს. ასეთ შემთხვევაში, თუ ბრაუზერი არ არენდერებს არაფერს, ჩვენი ნაკადი ტყუილად არ იქნება მუდმივად გაშვებული თავისი მძიმე კალკულაციებით.

ჩემს შემთხვევაში საქმე უფრო ანიმაციასთან მაქვს და არა real-time თამაშთან. მდგომარეობები შემიძლია წინასწარვე გამოვთვალო, ამიტომ ასეთი გზა ავირჩიე: worker-ს ვუშვებ რომელიც მთლიადანვე აგენერირებს ყველა მდგომარეობას და თითოეულ მათგანს უკან აბრუნებს მთავარ ნაკადთან. requestAnimationFrame-ის ქოლბექი ტაიმსტემპს უყურებს და მაგის შესაბამისად გავლილ მდგომარეობებს არენდერებს.

ამდენი მდგომარეობების გენერაცია, აქეთ იქით გზავნა და შენახვა დიდად არ მომწონს, რადგან აზრზე არ ვარ ობიექტების რაოდენობის ზრდასთან ერთად რა მოხდება, თუმცა ჯერ ჯერობით უკეთესი გზა არ ვიცი.

JavaScript animation / game loop (ნაწილი 1)

იმ დღეს ერთ-ერთ ვებ აპლიკაციაში არც თუ ისე მარტივი ანიმაციის გაკეთება მომინდა და ბოლოს ჯავასკრიპტამდე მივედი. თუმცა რამდენიმე გზის გამოკვლევა დამჭირდა და გადავწყვიტე ამ თემაზე პოსტი კარგი იქნებოდა. იქნებ ვინმეს უკეთესი იდეა ჰქონდეს…

ჩემი პირველი ნაბიჯი CSS3-ის გამოცდა იყო. მაგის ანიმაციების მოდული ძალიან მომწონს და ვიფიქრე იქნებ როგორმე მის ფარგლებში ჩავმჯდარიყავი, მაგრამ როცა ანიმირებულ ელემენტებს შორის კოლიზიების დადგენა დამჭირდა, გავიჭედე 🙂

კოლიზიის შესახებ ინფორმაციას ბრაუზერი ვერ მაძლევს css ანიმაციის დროს (და არც მაქვს მაგის მოთხოვნის უფლება). შესაბამისად, როგორღაც მოძრავი ობიექტების ადგილსამყოფელის გაკონტროლება მჭირდებოდა. ეს ნიშნავს რომ რაღაც ინტერვალით ვამოწმო თითოეული მათგანის კოორდინატი და თანაკვეთა შევამოწმო, ამასთან ინტერვალებში არ გადავახტე კოლიზიის მომენტს.

როგორც აღმოჩნდა, მოძრავი ობიექტის კოორდინატებიც არ არის ზედაპირზე. თუ ვინმეს დაგჭირდებათ, ვებკიტში არის ერთი ადგილი საიდანაც მათი აღება შეიძლება – ეს არის ტრანსფორმაციის მატრიცა, რომელიც ახალი მდგომარეობის გამოსათვლელად გამოიყენება. ეს მატრიცა შეიცავს მიმდინარე კოორდინატების ინფორმაციასაც. მაგალითად:

var animatedElement = document.getElementById("animatedElement");
var transform = window.getComputedStyle(animatedElement).webkitTransform;
var curTransform = new WebKitCSSMatrix(transform);
console.log(animatedElement.offsetLeft + curTransform.m41);
console.log(animatedElement.offsetTop + curTransform.m42);

ამიტომ გადავწყვიტე ჩემს შემთხვევაში მხოლოდ CSS3-ის იმედზე ვერ ვიქნებოდი და ისევ ჯავასკრიპტს მოვკიდე ხელი. იდეაში საჭიროა შემდეგი: უნდა გავაკეთო კადრების ციკლი. დროის პატარა ინტერვალებში უნდა მოხდეს ელემენტების მდგომარეობის / მდებარეობის განახლება და შესაბამისად გადახატვა. კოლიზიების შემოწმებაც მანდ შეიძლება – მდგომარეობის გენერაციის დროს.

წინათ ჯავასკრიპტის ანიმაცია რომ გაგეკეთებინათ, ერთმანეთში ჩადგმული და გადაბმული setTimeout ან setInteval მეთოდებით მოგიწევდათ ანიმაციის ციკლის შეკვრა. მაგალითად, ასე:

function draw(){
    console.log('timestamp ' + +new Date());
    // update positions and redraw frame
}
setInterval(draw, 10);

ყოველ 10 მილიწამში მოხდება draw მეთოდის გამოძახება და მაგ მეთოდში იქნებოდა ანიმაციის ახალი მდგომარეობის გენერაცია.

ზოგი ახლაც იყენებს ამ გზას მარტივი ამოცანების გადასაჭრელად, თუმცა გასათვალისწინებელია მათი პრობლემები:

1. არაზუსტი დრო. დაყოვნება, რომელსაც ამ მეთოდებს (setTimeout ან setInteval) მილიწამების სახით გადავცემთ არგუმენტად, მხოლოდ იმას ნიშნავს, რომ ჩვენი ფუნქციები ამ დროის შემდეგ ბრაუზერის UI thread queue-ში ჩაემატებიან და თავის რიგს დაელოდებიან შესასრულებლად. შესაბამისად თუ UI ნაკადი სხვა საქმითაა დაკავებული, ჩვენი ანიმაციის ფრაგმენტი ზუსტად საჭირო დროს ვერ შესრულდება.

2. Browser timer resolution. მისი ქართული შესატყვისი ვერ მოვიფიქრე – ეს არის დრო, რამდენ ხანში ერთხელაც ახლდება საათი. მაგალითად, წინათ ბრაუზერები OS-ის სისტემურ დროს უყურებდნენ, რომელსაც ეს რეზოლუცია გაცილებით დიდი ჰქონდა (მაგალითად 10-15 მილიწამი), შესაბამისად რამდენიმე მილიწამის მითითების შემთხვევაში, შედეგის განსაზღვრა ვერ ხდებოდა. დღეს მთლად ასე აღარ არის, მაგრამ ამაზე ლაპარაკი შორს წამიყვანს.

მოკლედ რომ ვთქვათ, არ იყო გარანტია თუ როდის გადახატავდა ბრაუზერი ეკრანს.
შემდეგ მოზილას ერთ-ერთი თანამშრომელი მივიდა ახალ იდეამდე და Firefox-ში ჩაემატა მეთოდი mozRequestAnimationFrame, რომელიც შემდეგ სხვა ბრაუზერებმაც გადაიღეს და სპეციფიკაციაში requestAnimationFrame სახელით შევიდა.

ამ მეთოდს მხოლოდ ერთი პარამეტრი აქვს – თქვენი ფუნქცია, რომელსაც ბრაუზერი ეკრანის გადახატვამდე გამოიძახებს. ხოლო თქვენს ქოლბექს გამოძახების დროს გადმოეცემა მაღალი სიზუსტის ტაიმსტემპი – დრო როცა requestAnimationFrame-მა ქოლბექების გამოძახება დაიწყო მიმდინარე კადრისთვის.

მაგალითად:

function step(timestamp) {
    console.log(timestamp);
    requestAnimationFrame(step);
}
requestAnimationFrame(step);

requestAnimationFrame-ს გამოძახებით მხოლოდ მომდევნო გადახატვაზე გამოიძახება ქოლბექი. რადგან ჩვენ ანიმაციის ციკლი გვჭირდება, მოგვიწევს რომ ქოლბექში შიგნით ხელახლა მივებათ ბრაუზერის მოვლენას.

ეს მაღალი სიზუსტის ტაიმსტემპი არის DOMHighResTimeStamp ობიექტი და განსხვავდება ჩვეულებრივი Date.now() ფუნქციის დასაბრუნებელი მნიშვნელობისგან. თუ მაგ ფორმატის მიმდინარე ტაიმსტემპი გჭირდებათ, მისი მიღება შეიძლება performance.now() მეთოდით.