1 Open source PDF viewers
Open source PDF viewers are catching up!
The best open source alternative is currently perhaps PDF.js, Mozilla's PDF viewer for Firefox. After fixing issues on both PDF.js and animate
's sides, animations are now rendered well and with good performance in terms of achievable frame rates. The minimum requirements are PDF.js v4.4.168 and animate
v2024/09/05. Firefox 130 is the first browser release with a bundled PDF.js that supports animations. Another requirement is using animate
's method=widget
option. The package manual was built with this option and can be used for testing: animate.pdf
.
Local PDF files can be opened by running firefox <file.pdf>
on the command-line or by right-click on a PDF in a file explorer.
Users of Chromium and of browsers building on it, such as Chrome or Edge, may run PDF.js from
https://mozilla.github.io/pdf.js/web/viewer.html?file=
and view local PDF files. A local PDF file is opened via the >> button at the right end of the toolbar and selecting "Open".
Okular from KDE also supports JavaScript-driven animations. See:
https://community.kde.org/GSoC/2019/StatusReports/Jo%C3%A3oNetto
Tested with Okular-1.10 (KDE-20.04.1).
2 beamer
class document in SVG format
Think about giving up the PDF format in favour of SVG.
Animations run in all contemporary Web browsers, while Blink-based ones (Chromium, Chrome, Edge, Opera) have the best performance. Also run on mobile devices.
With SVG as output format, video and YouTube embeds are possible too, using package media4svg
; see: MP4 in presentation in a non-broken way . And even animated Gif.
Click the image to start presentation in the Web browser and press F11 for full-screen. Navigate through slides with PgUp and PgDown, mouse left or right click, or mouse wheel.
Compile the example code with
latex svgbeamer
latex svgbeamer
dvisvgm --zoom=-1 --font-format=woff2 --bbox=papersize --page=1- --linkmark=none svgbeamer
Example code svgbeamer.tex
:
\documentclass[dvisvgm,hypertex,aspectratio=169]{beamer}
\usefonttheme{serif}
\usepackage{animate}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% slide navigation via keyboard and mouse left-or-right-click/mouse wheel,
% mouse cursor autohide on idle; navigation symbols <--, -->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\usepackage{fontawesome5}
\setbeamertemplate{navigation symbols}{}
\AddToHook{shipout/before}{\xdef\currentPageNumber{\inteval{\ReadonlyShipoutCounter+1}}}
\AddToHook{shipout/foreground}{%
\put(0,0){%
\raisebox{-\dimexpr\height+0.5ex\relax}[0pt][0pt]{\makebox[\paperwidth][r]{%
\normalsize\color{structure!40!}%
\ifnum\currentPageNumber>1%
\href{\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber-1\relax}.svg}{\faArrowLeft}%
\else%
\textcolor{lightgray}{\faArrowLeft}%
\fi\hspace{0.5ex}%
\ifnum\currentPageNumber<\PreviousTotalPages%
\href{\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber+1\relax}.svg}{\faArrowRight}%
\else%
\textcolor{lightgray}{\faArrowRight}%
\fi%
\hspace{0.5ex}%
}}%
}%
\special{dvisvgm:raw
<style>svg{cursor: none}</style>%
<defs>%
<script type="text/javascript">%
<![CDATA[%
var cursorTimer;%
var downOnLink=false;%
var downOnRoot=false;%
function islink(tg){return (tg.tagName=='a') ? true : tg.parentNode ? islink(tg.parentNode) : false;};%
function ismmedia(tg){return (tg.tagName=='video'||tg.tagName=='audio') ? true : tg.parentNode ? ismmedia(tg.parentNode) : false;};%
document.addEventListener('mousemove', function(e){%
if(islink(e.target)||ismmedia(e.target)||e.target.getAttribute('class')=='annot'){%
e.target.style.cursor='pointer';}else{e.target.style.cursor='default';}%
try{clearTimeout(cursorTimer);}catch(err){};%
cursorTimer=setTimeout(function(){e.target.style.cursor='none';},3000);%
});%
window.addEventListener('contextmenu', function(e){% capture right click
if(!islink(e.target)&&!ismmedia(e.target)) e.preventDefault();%
});%
document.addEventListener('mousedown', function(e){%
if(islink(e.target)||ismmedia(e.target)) downOnLink=true;%
else downOnRoot=true;%
});%
document.addEventListener('mouseup', function(e){%
if(downOnLink||!downOnRoot){downOnLink=false;return;}%
downOnRoot=false;%
\ifnum\currentPageNumber<\PreviousTotalPages
if(!e.shiftKey&&e.button==0) document.location.replace('\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber+1\relax}.svg');%
\fi%
\ifnum\currentPageNumber>1
if(e.shiftKey||e.button>1) document.location.replace('\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber-1\relax}.svg');%
\fi%
});%
document.addEventListener('wheel', function(e){%
\ifnum\currentPageNumber<\PreviousTotalPages
if(e.deltaY>0){%
document.location.replace('\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber+1\relax}.svg');%
}%
\fi%
\ifnum\currentPageNumber>1
if(e.deltaY<0){%
document.location.replace('\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber-1\relax}.svg');%
}%
\fi%
});%
document.addEventListener('keydown',function(e){%
\ifnum\currentPageNumber<\PreviousTotalPages
if(e.key=='PageDown'||e.key=='ArrowDown'||e.key=='ArrowRight')%
document.location.replace('\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber+1\relax}.svg');%
\fi%
\ifnum\currentPageNumber>1
if(e.key=='PageUp'||e.key=='ArrowUp'||e.key=='ArrowLeft')%
document.location.replace('\jobname-\zeroPad{\PreviousTotalPages}{\the\numexpr\currentPageNumber-1\relax}.svg');%
\fi%
if(e.key=='Home')
document.location.replace('\jobname-\zeroPad{\PreviousTotalPages}{1}.svg');%
if(e.key=='End')
document.location.replace('\jobname-\PreviousTotalPages.svg');%
});%
]]>%
</script>%
</defs>%
}%
}%
% helper macro \zeroPad : zero-pads integer according to template,
% e. g. 123 --> 00123 if template is `99999`
% #1: arbitrary integer number as template specifying the
% width, e. g. `987654' for a width of 6 digits
% #2: the number to be formatted
\def\zeroPad#1#2{\zeroPadI{\zeroTemplate{0}{#1}}{#2}}
%low level macros used by \zeroPad
\def\zeroPadI#1#2{% #1: string of zeros specifying width, #2 number
\ifnum1#2<1#1
\zeroPadI{#1}{0#2}%
\else%
#2%
\fi%
}%
\def\zeroTemplate#1#2{% create template (string of zeros) from given num
\ifnum10#1>1#2
#1%
\else%
\zeroTemplate{0#1}{#2}%
\fi%
}%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%required by PSTricks example
\usepackage[dvipsnames,svgnames]{pstricks}
\usepackage{pst-node,pst-plot,pst-eucl}
\usepackage{pst-solides3d}
\usepackage{multido}
\usepackage[nomessages]{fp}
\title{Using package \emph{animate} without Adobe}
\subtitle{Use a Web browser and press \framebox{F11}}
\author{AlexG}
\date{\today}
\begin{document}
\frame{\titlepage}
\begin{frame}{Animation}
trivial example
\begin{center}
\begin{animateinline}[controls]{2}
\multiframe{10}{i=0+1}{\Huge\fbox{\i}}
\end{animateinline}
\end{center}
\end{frame}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% PSTricks example by J. Gilg
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\R{4} % Radius des festen Kreises (Length fixed Circle)
\def\r{1} % Radius des abrollenden Kreises (Length rolling Circle)
\def\A{0.75} % Abstand erzeugenden Punkt zu Mittelpunkt des abrollenden Kreises (Length Pointer)
\def\winkel{360} % Winkel: 1 Umlauf entspricht 360 (Angle: 1 revolution corresponds to 360)
\FPdiv{\myDeltaA}{\winkel}{90}%
\def\psBall{\rput(0,0){\psSolid[
object=sphere,r=0.35,
hue=0 1,
RotX=\ai\space,ngrid=12](0,0,0)}}
\def\myFigure{%
\pstVerb{% erzeugender Punkt (parameterized Hypocycloide)
/Xcoord \ai\space cos \R\space \r\space sub mul \ai\space \R\space \r\space sub \r\space div mul cos \A\space mul add def % (R-r)cos(a)+A cos[(R/r-1) a]
/Ycoord \ai\space sin \R\space \r\space sub mul \ai\space \R\space \r\space sub \r\space div mul sin \A\space mul sub def % (R-r)sin(a)-A sin[(R/r-1) a]
}%
% erzeugender Punkt (generating point)
\rput(0,0){\rput(!Xcoord Ycoord){\psBall}}%
\rput(0,0){\psSolid[object=cube,ngrid=4,
hue=0 1,
hollow,
a=0.5,
RotZ=-\ai\space](0,0,0)%
}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[t]{PSTricks Animation}
At the end of a rainbow \dots, by Jürgen Gilg.
\begin{itemize}
\item best viewed in Blink-based browsers, such as Chromium, Chrome, Opera
\item frame rate printed to JavaScript console (Ctrl+Shift+I)
\end{itemize}
\begin{center}
\raisebox{0pt}[0.65\height][0pt]{%
\pstVerb{/clip {} def}% disable `clip' for much better SVG performance in Firefox
\begin{animateinline}[measure,loop,controls,height=0.8\textheight]{30}
\multiframe{90}{i=0+1}{%
\FPeval{ai}{myDeltaA*\i}%
\begin{pspicture}(-5,-5)(5,5)\myFigure\end{pspicture}%
}%
\end{animateinline}%
}
\end{center}
\end{frame}
\begin{frame}
\begin{center}\Huge The End\end{center}
\end{frame}
\end{document}
3 Animated SVG as file attachment to PDF
animate
can export to standalone SVG animations, as outlined in https://tex.stackexchange.com/a/136919 . The SVG can be embedded as a file attachment to the PDF, and be run in a Web browser on click or touch:
\usepackage{attachfile} % or attachfile2
...
\textattachfile{animatedImage.svg}{Click!}
or without being attached:
\usepackage{hyperref}
...
\href{run:animatedImage.svg}{Click!}
In either case, the operating system/window manager must be configured to use a Web browser as default application for opening SVG files.
Chromium-based Web browsers (e. g. Chrome, Opera) should be used for viewing animated SVG, because of their extremely good rendering performance. The Lorenz attractor may serve as a test example.
animate
.