Replace setInterval with requestAnimationFrame

This commit is contained in:
poongaloo 2026-06-08 19:15:54 +02:00
parent 045845c851
commit 7e7bd0b70a
7 changed files with 94 additions and 170 deletions

View File

@ -25,7 +25,8 @@ La compatibilité avec tous les navigateurs et appareils modernes n'est pas enco
- restaure les pages originales comme enfants directs ;
- restaure les attributs `style` et `class` d'origine ;
- supprime les données jQuery internes de turn.js sans effacer les données utilisateur sans lien.
- Correction de `animatef(false)` pour que l'annulation d'une animation nettoie bien l'intervalle actif.
- Remplacement de la boucle d'animation interne `setInterval` par `requestAnimationFrame`.
- Correction de `animatef(false)` pour que l'annulation d'une animation annule bien la frame planifiée.
- Ajout d'une démo navigateur minimale avec jQuery `4.0.0`.
- Ajout de texte et d'images locales dans la démo minimale.
- Correction de la mise en page de la démo minimale afin que les dimensions mesurées par turn.js correspondent à la taille réellement rendue des pages.
@ -167,6 +168,7 @@ La suite actuelle couvre :
- `addPage` / `removePage` dynamiques ;
- l'enregistrement des namespaces d'événements ;
- le nettoyage `destroy` et la réinitialisation ;
- la planification et l'annulation `requestAnimationFrame` dans `animatef` ;
- le rendu de la démo minimale ;
- le rendu après page tournée ;
- la géométrie en viewport compact ;

View File

@ -1,154 +0,0 @@
<!doctype html>
<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="../../turn.min.js"></script>
<style type="text/css">
body{
background:#ccc;
}
#book{
width:800px;
height:500px;
}
#book .turn-page{
background-color:white;
}
#book .cover{
background:#333;
}
#book .cover h1{
color:white;
text-align:center;
font-size:50px;
line-height:500px;
margin:0px;
}
#book .loader{
background-image:url(loader.gif);
width:24px;
height:24px;
display:block;
position:absolute;
top:238px;
left:188px;
}
#book .data{
text-align:center;
font-size:40px;
color:#999;
line-height:500px;
}
#controls{
width:800px;
text-align:center;
margin:20px 0px;
font:30px arial;
}
#controls input, #controls label{
font:30px arial;
}
#book .odd{
background-image:-webkit-linear-gradient(left, #FFF 95%, #ddd 100%);
background-image:-moz-linear-gradient(left, #FFF 95%, #ddd 100%);
background-image:-o-linear-gradient(left, #FFF 95%, #ddd 100%);
background-image:-ms-linear-gradient(left, #FFF 95%, #ddd 100%);
}
#book .even{
background-image:-webkit-linear-gradient(right, #FFF 95%, #ddd 100%);
background-image:-moz-linear-gradient(right, #FFF 95%, #ddd 100%);
background-image:-o-linear-gradient(right, #FFF 95%, #ddd 100%);
background-image:-ms-linear-gradient(right, #FFF 95%, #ddd 100%);
}
</style>
</head>
<body>
<div id="book">
<div class="cover"><h1>The Bible</h1></div>
</div>
<div id="controls">
<label for="page-number">Page:</label> <input type="text" size="3" id="page-number"> of <span id="number-pages"></span>
</div>
<script type="text/javascript">
// Sample using dynamic pages with turn.js
var numberOfPages = 1000;
// Adds the pages that the book will need
function addPage(page, book) {
// First check if the page is already in the book
if (!book.turn('hasPage', page)) {
// Create an element for this page
var element = $('<div />', {'class': 'page '+((page%2==0) ? 'odd' : 'even'), 'id': 'page-'+page}).html('<i class="loader"></i>');
// If not then add the page
book.turn('addPage', element, page);
// Let's assum that the data is comming from the server and the request takes 1s.
setTimeout(function(){
element.html('<div class="data">Data for page '+page+'</div>');
}, 1000);
}
}
$(window).ready(function(){
$('#book').turn({acceleration: true,
pages: numberOfPages,
elevation: 50,
gradients: !$.isTouch,
when: {
turning: function(e, page, view) {
// Gets the range of pages that the book needs right now
var range = $(this).turn('range', page);
// Check if each page is within the book
for (page = range[0]; page<=range[1]; page++)
addPage(page, $(this));
},
turned: function(e, page) {
$('#page-number').val(page);
}
}
});
$('#number-pages').html(numberOfPages);
$('#page-number').keydown(function(e){
if (e.keyCode==13)
$('#book').turn('page', $('#page-number').val());
});
});
$(window).bind('keydown', function(e){
if (e.target && e.target.tagName.toLowerCase()!='input')
if (e.keyCode==37)
$('#book').turn('previous');
else if (e.keyCode==39)
$('#book').turn('next');
});
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -25,7 +25,8 @@ Compatibility is not yet guaranteed for every modern browser or device. Safari/W
- restores original page elements as direct children;
- restores original `style` and `class` attributes;
- removes turn.js internal jQuery data while preserving unrelated user data.
- Fixed `animatef(false)` so cancelling an animation clears the active interval.
- Replaced the internal `setInterval` animation loop with `requestAnimationFrame`.
- Fixed `animatef(false)` so cancelling an animation cancels the scheduled animation frame.
- Added a minimal browser demo using jQuery `4.0.0`.
- Added text and local images to the minimal demo.
- Fixed minimal demo layout so turn.js page measurements match the real rendered page size.
@ -167,6 +168,7 @@ The current suite covers:
- dynamic `addPage` / `removePage`;
- event namespace registration;
- `destroy` cleanup and re-initialization;
- `requestAnimationFrame` scheduling and cancellation in `animatef`;
- minimal demo rendering;
- page turn rendering;
- compact viewport geometry;

View File

@ -11,7 +11,7 @@ const jqueryDist = dirname(require.resolve('jquery'));
const jquerySource = readFileSync(resolve(jqueryDist, 'jquery.js'), 'utf8');
const turnSource = readFileSync(resolve(__dirname, '../../turn.js'), 'utf8');
function createFixture(pageCount = 4) {
function createFixture(pageCount = 4, windowOptions = {}) {
const pages = Array.from({ length: pageCount }, (_, index) => (
`<div class="page">Page ${index + 1}</div>`
)).join('');
@ -22,6 +22,13 @@ function createFixture(pageCount = 4) {
url: 'http://localhost/'
});
Object.entries(windowOptions).forEach(([key, value]) => {
Object.defineProperty(dom.window, key, {
value,
configurable: true,
writable: true
});
});
dom.window.eval(jquerySource);
const $ = dom.window.jQuery;
dom.window.eval(turnSource);
@ -166,6 +173,53 @@ describe('turn.js jQuery plugin', () => {
expect($book.find('.turn-page-wrapper').length).toBeGreaterThan(0);
});
it('uses requestAnimationFrame for animatef animations', () => {
fixture.window.close();
const frameCallbacks = [];
let frameHandle = 0;
const requestAnimationFrame = vi.fn(callback => {
frameCallbacks.push(callback);
frameHandle += 1;
return frameHandle;
});
const cancelAnimationFrame = vi.fn();
fixture = createFixture(4, {
performance: { now: vi.fn(() => 0) },
requestAnimationFrame,
cancelAnimationFrame
});
const { $ } = fixture;
const frames = [];
const complete = vi.fn();
const $element = $('<div></div>');
$element.animatef({
from: 0,
to: 10,
duration: 32,
easing: (x, t, b, c, d) => b + c * (t / d),
frame: value => frames.push(value),
complete
});
expect(frames).toEqual([0]);
expect(requestAnimationFrame).toHaveBeenCalledTimes(1);
frameCallbacks.shift()(16);
expect(frames[1]).toBe(5);
expect(requestAnimationFrame).toHaveBeenCalledTimes(2);
$element.animatef(false);
expect(cancelAnimationFrame).toHaveBeenCalledWith(2);
frameCallbacks.shift()(32);
expect(frames).toEqual([0, 5]);
expect(complete).not.toHaveBeenCalled();
});
it('registers internal handlers with event namespaces', () => {
fixture.window.close();
fixture = createFixture();

44
turn.js
View File

@ -34,6 +34,20 @@ var has3d,
events = (isTouch) ? {start: 'touchstart', move: 'touchmove', end: 'touchend'}
: {start: 'mousedown', move: 'mousemove', end: 'mouseup'},
frameTime = (window.performance && window.performance.now) ?
function() { return window.performance.now(); } :
function() { return new Date().getTime(); },
requestFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) { return window.setTimeout(function() { callback(frameTime()); }, 16); },
cancelFrame = window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
function(handle) { window.clearTimeout(handle); },
turnEventNamespace = '.turn',
turnFlipEventNamespace = '.turnFlip',
@ -2008,7 +2022,7 @@ $.extend($.fn, {
var data = this.data();
if (data.effect)
clearInterval(data.effect.handle);
cancelFrame(data.effect.handle);
if (point) {
@ -2019,11 +2033,16 @@ $.extend($.fn, {
var j, diff = [],
len = point.to.length,
that = this,
fps = point.fps || 30,
time = - fps,
f = function() {
var j, v = [];
time = Math.min(point.duration, time + fps);
startTime = frameTime(),
f = function(timestamp) {
if (that.data().effect!==point)
return;
timestamp = timestamp || frameTime();
var time = Math.min(point.duration, Math.max(0, timestamp - startTime)),
j, v = [];
for (j = 0; j < len; j++)
v.push(point.easing(1, time, point.from[j], diff[j], point.duration));
@ -2031,24 +2050,25 @@ $.extend($.fn, {
point.frame((len==1) ? v[0] : v);
if (time==point.duration) {
clearInterval(data.effect.handle);
delete data['effect'];
that.data(data);
if (point.complete)
point.complete();
}
};
} else {
data.effect.handle = requestFrame(f);
that.data(data);
}
};
for (j = 0; j < len; j++)
diff.push(point.to[j] - point.from[j]);
data.effect = point;
data.effect.handle = setInterval(f, fps);
this.data(data);
f();
f(startTime);
} else {
if (data.effect && data.effect.handle)
clearInterval(data.effect.handle);
cancelFrame(data.effect.handle);
delete data['effect'];
}
}

2
turn.min.js vendored

File diff suppressed because one or more lines are too long