Sepertinya requestAnimationFrame
adalah de facto cara untuk menghidupkan hal-hal yang sekarang. Ini bekerja cukup baik bagi saya untuk sebagian besar, tapi sekarang saya'm mencoba untuk melakukan beberapa kanvas animasi dan aku bertanya-tanya: Apakah ada cara untuk memastikan itu berjalan di suatu fps? Saya mengerti bahwa tujuan dari rAF adalah untuk secara konsisten animasi yang halus, dan saya bisa menjalankan risiko membuat animasi berombak, tapi sekarang tampaknya untuk menjalankan secara drastis kecepatan yang berbeda cukup sewenang-wenang, dan I'm bertanya-tanya jika ada's cara untuk memerangi yang entah bagaimana.
I'd gunakan setInterval
tapi aku ingin optimasi bahwa rAF ini (terutama secara otomatis berhenti ketika tab di fokus).
Dalam kasus seseorang ingin melihat kode saya, it's cukup banyak:
animateFlash: function() {
ctx_fg.clearRect(0,0,canvasWidth,canvasHeight);
ctx_fg.fillStyle = 'rgba(177,39,116,1)';
ctx_fg.strokeStyle = 'none';
ctx_fg.beginPath();
for(var i in nodes) {
nodes[i].drawFlash();
}
ctx_fg.fill();
ctx_fg.closePath();
var instance = this;
var rafID = requestAnimationFrame(function(){
instance.animateFlash();
})
var unfinishedNodes = nodes.filter(function(elem){
return elem.timer < timerMax;
});
if(unfinishedNodes.length === 0) {
console.log("done");
cancelAnimationFrame(rafID);
instance.animate();
}
}
Di Mana Node.drawFlash() adalah hanya beberapa kode yang menentukan radius didasarkan dari variabel counter dan kemudian menarik sebuah lingkaran.
Cara throttle requestAnimationFrame tertentu frame rate
Demo throttling pada 5 FPS:
Metode ini bekerja dengan pengujian waktu yang telah berlalu sejak mengeksekusi frame terakhir loop.
Gambar kode mengeksekusi hanya bila anda ditentukan FPS interval yang telah berlalu.
Bagian pertama dari kode menetapkan beberapa variabel yang digunakan untuk menghitung waktu yang telah berlalu.
var stop = false;
var frameCount = 0;
var $results = $("#results");
var fps, fpsInterval, startTime, now, then, elapsed;
// initialize the timer variables and start the animation
function startAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
animate();
}
Dan ini kode aktual requestAnimationFrame loop yang menarik pada anda ditentukan FPS.
// the animation loop calculates time elapsed since the last loop
// and only draws if your specified fps interval is achieved
function animate() {
// request another frame
requestAnimationFrame(animate);
// calc elapsed time since last loop
now = Date.now();
elapsed = now - then;
// if enough time has elapsed, draw the next frame
if (elapsed > fpsInterval) {
// Get ready for next frame by setting then=now, but also adjust for your
// specified fpsInterval not being a multiple of RAF's interval (16.7ms)
then = now - (elapsed % fpsInterval);
// Put your drawing code here
}
}
Update 2016/6
Masalah throttling frame rate adalah bahwa layar telah konstan tingkat update, biasanya 60 FPS.
Jika kita ingin 24 FPS kita tidak akan pernah mendapatkan yang benar 24 fps pada layar, kita dapat waktu seperti itu tapi tidak menunjukkan sebagai monitor hanya dapat menampilkan disinkronkan frame pada 15 fps, 30 fps atau 60 fps (beberapa monitor juga 120 fps).
Namun, untuk waktu tujuan kita dapat menghitung dan memperbarui bila mungkin.
Anda dapat membangun semua logika untuk mengendalikan frame-rate oleh encapsulating perhitungan dan callback ke suatu objek:
function FpsCtrl(fps, callback) {
var delay = 1000 / fps, // calc. time per frame
time = null, // start time
frame = -1, // frame count
tref; // rAF time reference
function loop(timestamp) {
if (time === null) time = timestamp; // init start time
var seg = Math.floor((timestamp - time) / delay); // calc frame no.
if (seg > frame) { // moved to next frame?
frame = seg; // update
callback({ // callback function
time: timestamp,
frame: frame
})
}
tref = requestAnimationFrame(loop)
}
}
Kemudian tambahkan beberapa controller dan konfigurasi kode:
// play status
this.isPlaying = false;
// set frame-rate
this.frameRate = function(newfps) {
if (!arguments.length) return fps;
fps = newfps;
delay = 1000 / fps;
frame = -1;
time = null;
};
// enable starting/pausing of the object
this.start = function() {
if (!this.isPlaying) {
this.isPlaying = true;
tref = requestAnimationFrame(loop);
}
};
this.pause = function() {
if (this.isPlaying) {
cancelAnimationFrame(tref);
this.isPlaying = false;
time = null;
frame = -1;
}
};
Hal ini menjadi sangat sederhana - sekarang, semua yang kita harus lakukan adalah untuk membuat sebuah instance dengan menetapkan fungsi callback dan frame rate yang diinginkan seperti ini:
var fc = new FpsCtrl(24, function(e) {
// render each frame here
});
Kemudian mulai (yang bisa menjadi perilaku default jika diinginkan):
fc.start();
Yang's ini, semua logika ditangani secara internal.
var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0;
ctx.font = "20px sans-serif";
// update canvas with some information and animation
var fps = new FpsCtrl(12, function(e) {
ctx.clearRect(0, 0, c.width, c.height);
ctx.fillText("FPS: " + fps.frameRate() +
" Frame: " + e.frame +
" Time: " + (e.time - pTime).toFixed(1), 4, 30);
pTime = e.time;
var x = (pTime - mTime) * 0.1;
if (x > c.width) mTime = pTime;
ctx.fillRect(x, 50, 10, 10)
})
// start the loop
fps.start();
// UI
bState.onclick = function() {
fps.isPlaying ? fps.pause() : fps.start();
};
sFPS.onchange = function() {
fps.frameRate(+this.value)
};
function FpsCtrl(fps, callback) {
var delay = 1000 / fps,
time = null,
frame = -1,
tref;
function loop(timestamp) {
if (time === null) time = timestamp;
var seg = Math.floor((timestamp - time) / delay);
if (seg > frame) {
frame = seg;
callback({
time: timestamp,
frame: frame
})
}
tref = requestAnimationFrame(loop)
}
this.isPlaying = false;
this.frameRate = function(newfps) {
if (!arguments.length) return fps;
fps = newfps;
delay = 1000 / fps;
frame = -1;
time = null;
};
this.start = function() {
if (!this.isPlaying) {
this.isPlaying = true;
tref = requestAnimationFrame(loop);
}
};
this.pause = function() {
if (this.isPlaying) {
cancelAnimationFrame(tref);
this.isPlaying = false;
time = null;
frame = -1;
}
};
}
body {font:16px sans-serif}
<label>Framerate: <select id=sFPS>
<option>12</option>
<option>15</option>
<option>24</option>
<option>25</option>
<option>29.97</option>
<option>30</option>
<option>60</option>
</select></label><br>
<canvas id=c height=60></canvas><br>
<button id=bState>Start/Stop</button>
Lama menjawab
Tujuan utama dari requestAnimationFrame
adalah untuk melakukan sinkronisasi update ke monitor's refresh rate. Ini akan memerlukan anda untuk menghidupkan di FPS monitor atau faktor itu (ie. 60, 30, 15 FPS untuk khas refresh rate @ 60 Hz).
Jika anda ingin lebih sewenang-wenang FPS maka tidak ada gunanya menggunakan rAF sebagai frame rate tidak akan pernah cocok dengan monitor's frekuensi update lagian (hanya frame di sana-sini) yang hanya tidak dapat memberikan animasi yang halus (seperti dengan semua frame re-timing) dan anda dapat mungkin juga menggunakan setTimeout
atau setInterval
sebagai gantinya.
Ini juga masalah yang diketahui dalam video profesional industri ketika anda ingin pemutaran video pada berbagai FPS maka perangkat akan menunjukkan itu di refresh. Banyak teknik telah digunakan seperti frame blending dan kompleks re-timing re-building frame menengah didasarkan pada gerakan vektor, tetapi dengan kanvas teknik ini tidak tersedia dan hasilnya akan selalu menjadi dendeng video.
var FPS = 24; /// "silver screen"
var isPlaying = true;
function loop() {
if (isPlaying) setTimeout(loop, 1000 / FPS);
... code for frame here
}
Alasan mengapa kita setTimeout
pertama (dan mengapa beberapa tempat rAF
pertama ketika poli-isi digunakan) adalah bahwa ini akan menjadi lebih akurat sebagai setTimeout
akan antrian acara segera ketika loop dimulai sehingga tidak peduli berapa banyak waktu yang tersisa kode yang akan digunakan (disediakan itu doesn't melebihi batas waktu interval) panggilan berikutnya akan berada pada interval yang diwakilinya (murni rAF ini tidak penting sebagai rAF akan mencoba untuk melompat ke frame berikutnya dalam hal apapun).
Juga layak untuk dicatat bahwa menempatkan hal pertama juga akan risiko panggilan susun sebagai dengan setInterval
. setInterval
mungkin sedikit lebih akurat untuk penggunaan ini.
Dan anda dapat menggunakan setInterval
bukan luar loop untuk melakukan hal yang sama.
var FPS = 29.97; /// NTSC
var rememberMe = setInterval(loop, 1000 / FPS);
function loop() {
... code for frame here
}
Dan untuk menghentikan loop:
clearInterval(rememberMe);
Dalam rangka untuk mengurangi frame rate ketika tab menjadi kabur anda dapat menambahkan faktor seperti ini:
var isFocus = 1;
var FPS = 25;
function loop() {
setTimeout(loop, 1000 / (isFocus * FPS)); /// note the change here
... code for frame here
}
window.onblur = function() {
isFocus = 0.5; /// reduce FPS to half
}
window.onfocus = function() {
isFocus = 1; /// full FPS
}
Dengan cara ini anda dapat mengurangi FPS untuk 1/4 dll.
Saya sarankan pembungkus panggilan anda untuk requestAnimationFrame
dalam setTimeout
. Jika anda menelepon setTimeout
dari dalam fungsi dari mana anda diminta animasi frame, anda're mengalahkan tujuan requestAnimationFrame
. Tetapi jika anda menelepon requestAnimationFrame
dari dalam setTimeout
bekerja dengan lancar:
var fps = 25
function animate() {
setTimeout(function() {
requestAnimationFrame(animate);
}, 1000 / fps);
}
Ini semua adalah ide-ide bagus dalam teori, sampai anda pergi jauh. Masalahnya adalah anda dapat't throttle RAF tanpa de-sinkronisasi itu, mengalahkan itu's tujuan yang ada. Jadi anda membiarkan hal itu berjalan pada kecepatan penuh, dan memperbarui data dalam sebuah loop terpisah, , atau bahkan sebuah thread terpisah!
Ya, saya mengatakan itu. Anda can melakukan multi-threaded JavaScript di browser!
Ada dua metode yang saya tahu pekerjaan yang sangat baik tanpa jank, menggunakan jauh lebih sedikit jus dan menciptakan panas yang lebih sedikit. Manusia akurat skala waktu dan efisiensi mesin adalah hasil bersih.
Maaf jika ini adalah sedikit bertele-tele, tapi here goes...
Cara 1: Update data melalui setInterval, dan grafis melalui RAF.
Terpisah setInterval untuk memperbarui terjemahan dan rotasi nilai-nilai, fisika, tabrakan, dll. Menjaga nilai-nilai tersebut dalam suatu objek untuk setiap elemen animasi. Menetapkan mengubah string ke suatu variabel di masing-masing objek setInterval 'frame'. Jauhkan benda-benda dalam sebuah array. Mengatur interval yang anda inginkan fps di ms: ms=(1000/fps). Hal ini membuat mantap jam yang memungkinkan sama fps pada perangkat apapun, terlepas dari RAF kecepatan. jangan menetapkan untuk mengubah unsur-unsur berikut!
Dalam requestAnimationFrame loop, iterate melalui array dengan tua-sekolah untuk loop-- tidak menggunakan bentuk-bentuk baru di sini, mereka lambat!
for(var i=0; i<sprite.length-1; i++){ rafUpdate(sprite[i]); }
Di rafUpdate fungsi, bisa mengubah string dari js objek di dalam array, dan unsur-unsurnya id. Anda harus sudah memiliki 'sprite' unsur-unsur yang melekat pada sebuah variabel atau dapat diakses dengan mudah melalui cara-cara lain sehingga anda don't kehilangan waktu 'mendapatkan'-ing mereka di RAF. Menjaga mereka dalam sebuah benda bernama setelah mereka id html's bekerja dengan cukup baik. Set bagian atas bahkan sebelum masuk ke SI atau RAF.
Gunakan RAF untuk memperbarui anda mengubah only, hanya menggunakan transformasi 3D (bahkan untuk 2d), dan mengatur css "akan-berubah: mengubah;" pada unsur-unsur yang akan berubah. Hal ini membuat anda mengubah disinkronisasikan ke native refresh rate sebanyak mungkin, tendangan GPU, dan memberitahu browser mana untuk berkonsentrasi paling.
Jadi, anda harus memiliki sesuatu seperti ini pseudocode...
// refs to elements to be transformed, kept in an array
var element = [
mario: document.getElementById('mario'),
luigi: document.getElementById('luigi')
//...etc.
]
var sprite = [ // read/write this with SI. read-only from RAF
mario: { id: mario ....physics data, id, and updated transform string (from SI) here },
luigi: { id: luigi .....same }
//...and so forth
] // also kept in an array (for efficient iteration)
//update one sprite js object
//data manipulation, CPU tasks for each sprite object
//(physics, collisions, and transform-string updates here.)
//pass the object (by reference).
var SIupdate = function(object){
// get pos/rot and update with movement
object.pos.x += object.mov.pos.x; // example, motion along x axis
// and so on for y and z movement
// and xyz rotational motion, scripted scaling etc
// build transform string ie
object.transform =
'translate3d('+
object.pos.x+','+
object.pos.y+','+
object.pos.z+
') '+
// assign rotations, order depends on purpose and set-up.
'rotationZ('+object.rot.z+') '+
'rotationY('+object.rot.y+') '+
'rotationX('+object.rot.x+') '+
'scale3d('.... if desired
; //...etc. include
}
var fps = 30; //desired controlled frame-rate
// CPU TASKS - SI psuedo-frame data manipulation
setInterval(function(){
// update each objects data
for(var i=0; i<sprite.length-1; i++){ SIupdate(sprite[i]); }
},1000/fps); // note ms = 1000/fps
// GPU TASKS - RAF callback, real frame graphics updates only
var rAf = function(){
// update each objects graphics
for(var i=0; i<sprite.length-1; i++){ rAF.update(sprite[i]) }
window.requestAnimationFrame(rAF); // loop
}
// assign new transform to sprite's element, only if it's transform has changed.
rAF.update = function(object){
if(object.old_transform !== object.transform){
element[object.id].style.transform = transform;
object.old_transform = object.transform;
}
}
window.requestAnimationFrame(rAF); // begin RAF
Hal ini membuat anda update ke data objek dan mengubah string disinkronkan dengan yang diinginkan 'frame' tingkat di SI, dan sebenarnya mengubah tugas-tugas di RAF disinkronkan dengan GPU refresh rate. Jadi sebenarnya grafis update hanya di RAF, tapi perubahan data, dan membangun mengubah string yang di SI, dengan demikian tidak ada jankies tapi 'waktu' arus yang diinginkan frame-rate.
Aliran:
[setup js sprite objects and html element object references]
[setup RAF and SI single-object update functions]
[start SI at percieved/ideal frame-rate]
[iterate through js objects, update data transform string for each]
[loop back to SI]
[start RAF loop]
[iterate through js objects, read object's transform string and assign it to it's html element]
[loop back to RAF]
Metode 2. Menempatkan SI di web-pekerja. Yang satu ini adalah FAAAST dan halus!
Sama seperti cara 1, tapi menempatkan SI di web-pekerja. It'll berjalan pada yang benar-benar terpisah benang kemudian, meninggalkan halaman untuk hanya berurusan dengan RAF dan UI. Lulus sprite array bolak-balik sebagai 'dipindahtangankan objek'. Ini adalah buko cepat. Tidak mengambil waktu untuk mengkloning atau cerita bersambung, tapi itu's tidak seperti lewat referensi dalam referensi dari sisi lain hancur, sehingga anda akan perlu untuk memiliki kedua sisi melewati ke sisi lain, dan hanya memperbarui mereka ketika hadir, seperti melewati catatan bolak-balik dengan teman pacar di sma.
Hanya satu orang yang bisa membaca dan menulis pada satu waktu. Ini baik-baik saja selama mereka memeriksa apakah itu's tidak terdefinisi untuk menghindari kesalahan. RAF lebih CEPAT dan akan menendang kembali dengan segera, kemudian pergi melalui banyak GPU frame hanya memeriksa jika itu's sudah dikirim belum kembali. SI di web-pekerja akan memiliki sprite array sebagian besar waktu, dan akan memperbarui posisi, gerakan dan fisika data, serta menciptakan baru mengubah string, kemudian menyebarkannya kembali ke RAF di halaman.
Ini adalah cara tercepat yang saya tahu untuk menghidupkan unsur-unsur melalui script. Dua fungsi ini akan berjalan sebagai dua program terpisah, di dua thread terpisah, mengambil keuntungan dari multi-core CPU's cara yang satu js script tidak. Multi-threaded animasi javascript.
Dan ia akan melakukannya dengan lancar tanpa jank, tapi sebenarnya ditentukan frame-rate, dengan sedikit perbedaan.
Hasilnya:
Salah satu dari dua metode ini akan memastikan anda skrip akan berjalan pada kecepatan yang sama pada setiap PC, ponsel, tablet, dll (hanya kemampuan perangkat dan browser, tentu saja).
Cara mudah throttle tertentu FPS:
// timestamps are ms passed since document creation.
// lastTimestamp can be initialized to 0, if main loop is executed immediately
var lastTimestamp = 0,
maxFPS = 30,
timestep = 1000 / maxFPS; // ms for each frame
function main(timestamp) {
window.requestAnimationFrame(main);
// skip if timestep ms hasn't passed since last frame
if (timestamp - lastTimestamp < timestep) return;
lastTimestamp = timestamp;
// draw frame here
}
window.requestAnimationFrame(main);
Sumber: Penjelasan Rinci dari Permainan JavaScript Loop dan Waktu oleh Isaac Sukin
Skipping requestAnimationFrame penyebab tidak halus(yang diinginkan) animasi pada custom fps.
// Input/output DOM elements
var $results = $("#results");
var $fps = $("#fps");
var $period = $("#period");
// Array of FPS samples for graphing
// Animation state/parameters
var fpsInterval, lastDrawTime, frameCount_timed, frameCount, lastSampleTime,
currentFps=0, currentFps_timed=0;
var intervalID, requestID;
// Setup canvas being animated
var canvas = document.getElementById("c");
var canvas_timed = document.getElementById("c2");
canvas_timed.width = canvas.width = 300;
canvas_timed.height = canvas.height = 300;
var ctx = canvas.getContext("2d");
var ctx2 = canvas_timed.getContext("2d");
// Setup input event handlers
$fps.on('click change keyup', function() {
if (this.value > 0) {
fpsInterval = 1000 / +this.value;
}
});
$period.on('click change keyup', function() {
if (this.value > 0) {
if (intervalID) {
clearInterval(intervalID);
}
intervalID = setInterval(sampleFps, +this.value);
}
});
function startAnimating(fps, sampleFreq) {
ctx.fillStyle = ctx2.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx2.fillRect(0, 0, canvas.width, canvas.height);
ctx2.font = ctx.font = "32px sans";
fpsInterval = 1000 / fps;
lastDrawTime = performance.now();
lastSampleTime = lastDrawTime;
frameCount = 0;
frameCount_timed = 0;
animate();
intervalID = setInterval(sampleFps, sampleFreq);
animate_timed()
}
function sampleFps() {
// sample FPS
var now = performance.now();
if (frameCount > 0) {
currentFps =
(frameCount / (now - lastSampleTime) * 1000).toFixed(2);
currentFps_timed =
(frameCount_timed / (now - lastSampleTime) * 1000).toFixed(2);
$results.text(currentFps + " | " + currentFps_timed);
frameCount = 0;
frameCount_timed = 0;
}
lastSampleTime = now;
}
function drawNextFrame(now, canvas, ctx, fpsCount) {
// Just draw an oscillating seconds-hand
var length = Math.min(canvas.width, canvas.height) / 2.1;
var step = 15000;
var theta = (now % step) / step * 2 * Math.PI;
var xCenter = canvas.width / 2;
var yCenter = canvas.height / 2;
var x = xCenter + length * Math.cos(theta);
var y = yCenter + length * Math.sin(theta);
ctx.beginPath();
ctx.moveTo(xCenter, yCenter);
ctx.lineTo(x, y);
ctx.fillStyle = ctx.strokeStyle = 'white';
ctx.stroke();
var theta2 = theta + 3.14/6;
ctx.beginPath();
ctx.moveTo(xCenter, yCenter);
ctx.lineTo(x, y);
ctx.arc(xCenter, yCenter, length*2, theta, theta2);
ctx.fillStyle = "rgba(0,0,0,.1)"
ctx.fill();
ctx.fillStyle = "#000";
ctx.fillRect(0,0,100,30);
ctx.fillStyle = "#080";
ctx.fillText(fpsCount,10,30);
}
// redraw second canvas each fpsInterval (1000/fps)
function animate_timed() {
frameCount_timed++;
drawNextFrame( performance.now(), canvas_timed, ctx2, currentFps_timed);
setTimeout(animate_timed, fpsInterval);
}
function animate(now) {
// request another frame
requestAnimationFrame(animate);
// calc elapsed time since last loop
var elapsed = now - lastDrawTime;
// if enough time has elapsed, draw the next frame
if (elapsed > fpsInterval) {
// Get ready for next frame by setting lastDrawTime=now, but...
// Also, adjust for fpsInterval not being multiple of 16.67
lastDrawTime = now - (elapsed % fpsInterval);
frameCount++;
drawNextFrame(now, canvas, ctx, currentFps);
}
}
startAnimating(+$fps.val(), +$period.val());
input{
width:100px;
}
#tvs{
color:red;
padding:0px 25px;
}
H3{
font-weight:400;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3>requestAnimationFrame skipping <span id="tvs">vs.</span> setTimeout() redraw</h3>
<div>
<input id="fps" type="number" value="33"/> FPS:
<span id="results"></span>
</div>
<div>
<input id="period" type="number" value="1000"/> Sample period (fps, ms)
</div>
<canvas id="c"></canvas><canvas id="c2"></canvas>
Kode asli by @tavnab.
Saya selalu melakukan hal ini dengan cara yang sangat sederhana tanpa bermain-main dengan cap waktu:
var fps, eachNthFrame, frameCount;
fps = 30;
//This variable specifies how many frames should be skipped.
//If it is 1 then no frames are skipped. If it is 2, one frame
//is skipped so "eachSecondFrame" is renderd.
eachNthFrame = Math.round((1000 / fps) / 16.66);
//This variable is the number of the current frame. It is set to eachNthFrame so that the
//first frame will be renderd.
frameCount = eachNthFrame;
requestAnimationFrame(frame);
//I think the rest is self-explanatory
fucntion frame() {
if (frameCount == eachNthFrame) {
frameCount = 0;
animate();
}
frameCount++;
requestAnimationFrame(frame);
}
Berikut ini's baik-baik penjelasan saya temukan: CreativeJS.com, untuk membungkus setTimeou) call di dalam fungsi diteruskan ke requestAnimationFrame. Keprihatinan saya dengan "biasa" requestionAnimationFrame akan sama, "bagaimana jika saya hanya ingin untuk menghidupkan tiga kali yang kedua?" Bahkan dengan requestAnimationFrame (sebagai lawan untuk setTimeout) adalah bahwa hal itu masih limbah (beberapa) jumlah "energi" (arti bahwa Browser kode untuk melakukan sesuatu, dan mungkin memperlambat sistem anda turun) 60 atau 120 atau namun berkali-kali kedua, sebagai lawan hanya dua atau tiga kali per detik (seperti yang mungkin anda inginkan).
Sebagian besar waktu saya menjalankan browser dengan JavaScript intentially off hanya untuk alasan ini. Tapi, aku'm menggunakan Yosemite 10.10.3, dan saya pikir ada's beberapa jenis timer masalah dengan itu - setidaknya pada sistem lama (relatif tua - arti 2011).