Hello everyone,
I'm facing a performance issue with a Flask web application on a **Raspberry Pi 5** and hoping for your help.
## Hardware & Setup
- **Raspberry Pi 5** (8GB RAM)
- **Raspberry Pi OS Lite** (fresh installation, no desktop)
- **Monitor via HDMI** connected
- **Kiosk mode** - should permanently display a web app
- **Auto-start on boot** configured
## The Application
- **Python Flask app** (Prayer Time display for mosque)
- **Image slideshow** with fade transitions (multiple sponsor images)
- **Text slider** (marquee-like, runs from right to left)
- **Live updates** via Server-Sent Events (SSE)
## The Problem
The **animations are not smooth**:
- Image transitions **stutter heavily**
- Text slider **jerks** while scrolling
- **CPU usage** at 40-50% during animations
## What I've Already Tried
### 1. Browser Alternatives Tested
- **Chromium**: Very choppy, high CPU usage
- **Firefox ESR**: Slightly better, but still stuttering
- **WebKit2GTK with Python**: Same performance issues
### 2. GPU Optimizations
```bash
# /boot/firmware/config.txt
gpu_mem=128
dtoverlay=vc4-kms-v3d
gpu_freq=500
```
### 3. CSS Optimizations
- **Marquee element** replaced with CSS animation
- **GPU acceleration** added (`transform: translate3d()`, `will-change`)
- **Hardware acceleration** enabled in WebKit
- **requestAnimationFrame** for slideshow transitions
### 4. JavaScript Optimizations
- **jQuery removed**, using vanilla JavaScript
- **DOM access reduced** (elements cached)
- **Debouncing** for SSE updates
- **Unnecessary libraries removed** (AOS, Portfolio-JS, etc.)
### 5. System Optimizations
- **Duplicate services removed** (were running in parallel)
- **Only one browser instance** ensured
- **WebKit with performance flags** configured
### 6. Auto-start Configuration
```bash
# /etc/systemd/system/prayer-kiosk.service
[Unit]
Description=Prayer Time Kiosk Application
After=graphical.target network-online.target
Wants=network-online.target
Requires=graphical.target
[Service]
Type=simple
User=hanau
Group=hanau
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/hanau/.Xauthority"
Environment="XDG_RUNTIME_DIR=/run/user/1000"
WorkingDirectory=/home/hanau
ExecStart=/usr/bin/python3 /home/hanau/prayer_webkit_kiosk.py
Restart=always
RestartSec=10
KillMode=mixed
TimeoutStopSec=30
[Install]
WantedBy=graphical.target
```
## Current Code
### Complete WebKit Kiosk Application
```python
#!/usr/bin/env python3
import gi
import os
import subprocess
import threading
import time
import sys
import psutil
# Set DISPLAY automatically if not set
if not os.environ.get('DISPLAY'):
os.environ['DISPLAY'] = ':0'
# Initialize GTK
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')
try:
from gi.repository import Gtk, WebKit2, GLib, Gdk
if not Gtk.init_check():
print("Error: GTK could not be initialized")
sys.exit(1)
except Exception as e:
print(f"Import error: {e}")
sys.exit(1)
class AutoPrayerKiosk:
def __init__(self):
print("Starting automatic Prayer Time Kiosk...")
self.start_flask_server()
self.create_fullscreen_window()
def start_flask_server(self):
"""Start Flask server automatically"""
def run_flask():
subprocess.run([
"bash", "-c",
"cd /home/hanau/prayertime/prayertimeV3 && source venv/bin/activate && python app.py"
])
flask_thread = threading.Thread(target=run_flask, daemon=True)
flask_thread.start()
time.sleep(8)
print("Flask server started automatically")
def create_fullscreen_window(self):
"""Create automatic fullscreen window"""
self.window = Gtk.Window()
self.window.set_title("Prayer Time Kiosk")
self.window.connect("destroy", Gtk.main_quit)
# AUTOMATIC FULLSCREEN - maximum size
self.window.set_decorated(False) # No title bar
self.window.set_resizable(False) # Not resizable
self.window.fullscreen() # Auto fullscreen
self.window.maximize() # Additionally maximize
# Auto-detect monitor size and adapt
screen = Gdk.Screen.get_default()
monitor_geometry = screen.get_monitor_geometry(0)
self.window.set_size_request(monitor_geometry.width, monitor_geometry.height)
# WebKit settings for best performance and fullscreen
settings = WebKit2.Settings()
settings.set_enable_javascript(True)
settings.set_enable_webgl(True)
settings.set_hardware_acceleration_policy(WebKit2.HardwareAccelerationPolicy.ALWAYS)
settings.set_enable_smooth_scrolling(True)
settings.set_enable_accelerated_2d_canvas(True)
settings.set_enable_developer_extras(False)
settings.set_zoom_text_only(False)
# Create WebView and configure fullscreen
self.webview = WebKit2.WebView()
self.webview.set_settings(settings)
# Adapt WebView completely to window
self.webview.set_size_request(monitor_geometry.width, monitor_geometry.height)
# Add WebView to window
self.window.add(self.webview)
# Load Prayer Time App
self.webview.load_uri("http://localhost:5000/prayertime")
# Show window - automatically fullscreen
self.window.show_all()
# Bring window to foreground and focus
self.window.present()
self.window.grab_focus()
print("Automatic fullscreen kiosk started")
# Hide cursor immediately (real kiosk mode)
GLib.timeout_add_seconds(1, self.hide_cursor)
# Disable screensaver
GLib.timeout_add_seconds(2, self.disable_screensaver)
def hide_cursor(self):
"""Hide mouse cursor automatically"""
display = Gdk.Display.get_default()
cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.BLANK_CURSOR)
if self.window.get_window():
self.window.get_window().set_cursor(cursor)
return False
def disable_screensaver(self):
"""Disable screensaver automatically"""
subprocess.run(["xset", "s", "off"], stderr=subprocess.DEVNULL)
subprocess.run(["xset", "-dpms"], stderr=subprocess.DEVNULL)
subprocess.run(["xset", "s", "noblank"], stderr=subprocess.DEVNULL)
return False
if __name__ == "__main__":
print("Starting automatic fullscreen kiosk...")
try:
kiosk = AutoPrayerKiosk()
Gtk.main()
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
```
### CSS Animation (Marquee Replacement)
```css
/* GPU-ACCELERATED TEXT SLIDER */
.slide-text .marquee-replacement p {
display: inline-block;
margin: 0;
/* GPU-accelerated animation */
animation: marqueeScroll 35s linear infinite;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
will-change: transform;
}
@keyframes marqueeScroll {
0% { transform: translate3d(100%, 0, 0); }
100% { transform: translate3d(-100%, 0, 0); }
}
/* GPU-ACCELERATED SLIDESHOW */
#slideshow {
transform: translateZ(0);
backface-visibility: hidden;
will-change: transform;
}
#slideshow > div {
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
will-change: transform, opacity;
}
#slideshow img {
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
image-rendering: optimizeSpeed;
will-change: transform;
}
```
### JavaScript Slideshow (Optimized)
```javascript
function initSlideshow(speed) {
let slideDiv = document.getElementById("slideshow");
if (!slideDiv) return;
let slides = slideDiv.querySelectorAll("div");
if (slides.length <= 1) {
slides.forEach((slide) => slide.style.display = "flex");
return;
}
slides.forEach((slide, index) => {
slide.style.display = index === 0 ? "flex" : "none";
slide.style.opacity = index === 0 ? "1" : "0";
});
let currentIndex = 0;
if (slideshowTimer) clearInterval(slideshowTimer);
slideshowTimer = setInterval(function () {
requestAnimationFrame(() => {
let slides = slideDiv.querySelectorAll("div");
let currentSlide = slides[currentIndex];
let nextIndex = (currentIndex + 1) % slides.length;
let nextSlide = slides[nextIndex];
// CSS transitions instead of jQuery for better performance
currentSlide.style.transition = "opacity 400ms ease-in-out";
currentSlide.style.opacity = "0";
setTimeout(() => {
currentSlide.style.display = "none";
nextSlide.style.display = "flex";
nextSlide.style.opacity = "0";
requestAnimationFrame(() => {
nextSlide.style.transition = "opacity 400ms ease-in-out";
nextSlide.style.opacity = "1";
});
}, 400);
currentIndex = nextIndex;
});
}, speed);
}
```
### Autostart Setup Commands
```bash
# Install minimal graphics environment
sudo apt install -y xserver-xorg-core xinit openbox lightdm
sudo apt install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0
# Configure autologin
sudo nano /etc/lightdm/lightdm.conf
# Add: autologin-user=hanau
# Set graphical boot
sudo systemctl set-default graphical.target
# Enable service
sudo systemctl enable prayer-kiosk.service
```
## System Information
```bash
# CPU usage during animations
~ 40-50% (WebKitWebProcess)
# GPU status
vcgencmd measure_temp
# temp=57.6'C
vcgencmd get_mem gpu
# gpu=128M
# No throttling
vcgencmd get_throttled
# throttled=0x0
# Memory
free -h
# total used free
# Mem: 7.8Gi 2.1Gi 5.4Gi
# Load average during animations
uptime
# load average: 1.70, 1.26, 0.60
```
## Questions to the Community
1. **Is Raspberry Pi 5 too weak** for such web animations in kiosk mode?
2. **Are there better browser alternatives** than WebKit/Chromium for animations?
3. **Should I switch to native application** (e.g., PyQt/Tkinter) instead of web browser?
4. **Additional GPU/system optimizations** I might have missed?
5. **Hardware-accelerated video overlay** possible for such applications?
6. **Different approach**: Use **Electron** or **Flutter** for better performance?
7. **Video-based solution**: Convert animations to **H.264 video** and use hardware decoder?
The app runs perfectly smooth on desktop PCs, only on the Pi it stutters. Any tips are welcome!
## Additional Information
- **Network**: Local (Flask server on same Pi)
- **Images**: ~500KB-1MB per sponsor image
- **Slideshow speed**: 5-8 seconds per image
- **Text slider**: ~35 seconds for complete run
- **Display resolution**: 1920x1080 @ 60Hz
- **Use case**: Mosque prayer time display (runs 24/7)
## What Works Well
- **Static content** displays perfectly
- **Clock updates** (every second) work smoothly
- **SSE updates** work without issues
- **Auto-boot and auto-start** work reliably
## What Doesn't Work Well
- **CSS transitions** (opacity, transform)
- **Scrolling text** (marquee-style animation)
- **Image slideshow** transitions
- **Any continuous animation** stutters
Has anyone successfully implemented smooth animations on Raspberry Pi 5 in kiosk mode? What's the best approach for such use cases?
Thank you for your help!
---
**Hardware**: Raspberry Pi 5 (8GB)
**OS**: Raspberry Pi OS Lite
**Browser**: WebKit2GTK, Chromium tested
**Issue**: Choppy CSS animations despite GPU optimizations
**Goal**: Smooth 24/7 kiosk display for mosque
I'm facing a performance issue with a Flask web application on a **Raspberry Pi 5** and hoping for your help.
## Hardware & Setup
- **Raspberry Pi 5** (8GB RAM)
- **Raspberry Pi OS Lite** (fresh installation, no desktop)
- **Monitor via HDMI** connected
- **Kiosk mode** - should permanently display a web app
- **Auto-start on boot** configured
## The Application
- **Python Flask app** (Prayer Time display for mosque)
- **Image slideshow** with fade transitions (multiple sponsor images)
- **Text slider** (marquee-like, runs from right to left)
- **Live updates** via Server-Sent Events (SSE)
## The Problem
The **animations are not smooth**:
- Image transitions **stutter heavily**
- Text slider **jerks** while scrolling
- **CPU usage** at 40-50% during animations
## What I've Already Tried
### 1. Browser Alternatives Tested
- **Chromium**: Very choppy, high CPU usage
- **Firefox ESR**: Slightly better, but still stuttering
- **WebKit2GTK with Python**: Same performance issues
### 2. GPU Optimizations
```bash
# /boot/firmware/config.txt
gpu_mem=128
dtoverlay=vc4-kms-v3d
gpu_freq=500
```
### 3. CSS Optimizations
- **Marquee element** replaced with CSS animation
- **GPU acceleration** added (`transform: translate3d()`, `will-change`)
- **Hardware acceleration** enabled in WebKit
- **requestAnimationFrame** for slideshow transitions
### 4. JavaScript Optimizations
- **jQuery removed**, using vanilla JavaScript
- **DOM access reduced** (elements cached)
- **Debouncing** for SSE updates
- **Unnecessary libraries removed** (AOS, Portfolio-JS, etc.)
### 5. System Optimizations
- **Duplicate services removed** (were running in parallel)
- **Only one browser instance** ensured
- **WebKit with performance flags** configured
### 6. Auto-start Configuration
```bash
# /etc/systemd/system/prayer-kiosk.service
[Unit]
Description=Prayer Time Kiosk Application
After=graphical.target network-online.target
Wants=network-online.target
Requires=graphical.target
[Service]
Type=simple
User=hanau
Group=hanau
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/hanau/.Xauthority"
Environment="XDG_RUNTIME_DIR=/run/user/1000"
WorkingDirectory=/home/hanau
ExecStart=/usr/bin/python3 /home/hanau/prayer_webkit_kiosk.py
Restart=always
RestartSec=10
KillMode=mixed
TimeoutStopSec=30
[Install]
WantedBy=graphical.target
```
## Current Code
### Complete WebKit Kiosk Application
```python
#!/usr/bin/env python3
import gi
import os
import subprocess
import threading
import time
import sys
import psutil
# Set DISPLAY automatically if not set
if not os.environ.get('DISPLAY'):
os.environ['DISPLAY'] = ':0'
# Initialize GTK
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')
try:
from gi.repository import Gtk, WebKit2, GLib, Gdk
if not Gtk.init_check():
print("Error: GTK could not be initialized")
sys.exit(1)
except Exception as e:
print(f"Import error: {e}")
sys.exit(1)
class AutoPrayerKiosk:
def __init__(self):
print("Starting automatic Prayer Time Kiosk...")
self.start_flask_server()
self.create_fullscreen_window()
def start_flask_server(self):
"""Start Flask server automatically"""
def run_flask():
subprocess.run([
"bash", "-c",
"cd /home/hanau/prayertime/prayertimeV3 && source venv/bin/activate && python app.py"
])
flask_thread = threading.Thread(target=run_flask, daemon=True)
flask_thread.start()
time.sleep(8)
print("Flask server started automatically")
def create_fullscreen_window(self):
"""Create automatic fullscreen window"""
self.window = Gtk.Window()
self.window.set_title("Prayer Time Kiosk")
self.window.connect("destroy", Gtk.main_quit)
# AUTOMATIC FULLSCREEN - maximum size
self.window.set_decorated(False) # No title bar
self.window.set_resizable(False) # Not resizable
self.window.fullscreen() # Auto fullscreen
self.window.maximize() # Additionally maximize
# Auto-detect monitor size and adapt
screen = Gdk.Screen.get_default()
monitor_geometry = screen.get_monitor_geometry(0)
self.window.set_size_request(monitor_geometry.width, monitor_geometry.height)
# WebKit settings for best performance and fullscreen
settings = WebKit2.Settings()
settings.set_enable_javascript(True)
settings.set_enable_webgl(True)
settings.set_hardware_acceleration_policy(WebKit2.HardwareAccelerationPolicy.ALWAYS)
settings.set_enable_smooth_scrolling(True)
settings.set_enable_accelerated_2d_canvas(True)
settings.set_enable_developer_extras(False)
settings.set_zoom_text_only(False)
# Create WebView and configure fullscreen
self.webview = WebKit2.WebView()
self.webview.set_settings(settings)
# Adapt WebView completely to window
self.webview.set_size_request(monitor_geometry.width, monitor_geometry.height)
# Add WebView to window
self.window.add(self.webview)
# Load Prayer Time App
self.webview.load_uri("http://localhost:5000/prayertime")
# Show window - automatically fullscreen
self.window.show_all()
# Bring window to foreground and focus
self.window.present()
self.window.grab_focus()
print("Automatic fullscreen kiosk started")
# Hide cursor immediately (real kiosk mode)
GLib.timeout_add_seconds(1, self.hide_cursor)
# Disable screensaver
GLib.timeout_add_seconds(2, self.disable_screensaver)
def hide_cursor(self):
"""Hide mouse cursor automatically"""
display = Gdk.Display.get_default()
cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.BLANK_CURSOR)
if self.window.get_window():
self.window.get_window().set_cursor(cursor)
return False
def disable_screensaver(self):
"""Disable screensaver automatically"""
subprocess.run(["xset", "s", "off"], stderr=subprocess.DEVNULL)
subprocess.run(["xset", "-dpms"], stderr=subprocess.DEVNULL)
subprocess.run(["xset", "s", "noblank"], stderr=subprocess.DEVNULL)
return False
if __name__ == "__main__":
print("Starting automatic fullscreen kiosk...")
try:
kiosk = AutoPrayerKiosk()
Gtk.main()
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
```
### CSS Animation (Marquee Replacement)
```css
/* GPU-ACCELERATED TEXT SLIDER */
.slide-text .marquee-replacement p {
display: inline-block;
margin: 0;
/* GPU-accelerated animation */
animation: marqueeScroll 35s linear infinite;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
will-change: transform;
}
@keyframes marqueeScroll {
0% { transform: translate3d(100%, 0, 0); }
100% { transform: translate3d(-100%, 0, 0); }
}
/* GPU-ACCELERATED SLIDESHOW */
#slideshow {
transform: translateZ(0);
backface-visibility: hidden;
will-change: transform;
}
#slideshow > div {
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
will-change: transform, opacity;
}
#slideshow img {
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
image-rendering: optimizeSpeed;
will-change: transform;
}
```
### JavaScript Slideshow (Optimized)
```javascript
function initSlideshow(speed) {
let slideDiv = document.getElementById("slideshow");
if (!slideDiv) return;
let slides = slideDiv.querySelectorAll("div");
if (slides.length <= 1) {
slides.forEach((slide) => slide.style.display = "flex");
return;
}
slides.forEach((slide, index) => {
slide.style.display = index === 0 ? "flex" : "none";
slide.style.opacity = index === 0 ? "1" : "0";
});
let currentIndex = 0;
if (slideshowTimer) clearInterval(slideshowTimer);
slideshowTimer = setInterval(function () {
requestAnimationFrame(() => {
let slides = slideDiv.querySelectorAll("div");
let currentSlide = slides[currentIndex];
let nextIndex = (currentIndex + 1) % slides.length;
let nextSlide = slides[nextIndex];
// CSS transitions instead of jQuery for better performance
currentSlide.style.transition = "opacity 400ms ease-in-out";
currentSlide.style.opacity = "0";
setTimeout(() => {
currentSlide.style.display = "none";
nextSlide.style.display = "flex";
nextSlide.style.opacity = "0";
requestAnimationFrame(() => {
nextSlide.style.transition = "opacity 400ms ease-in-out";
nextSlide.style.opacity = "1";
});
}, 400);
currentIndex = nextIndex;
});
}, speed);
}
```
### Autostart Setup Commands
```bash
# Install minimal graphics environment
sudo apt install -y xserver-xorg-core xinit openbox lightdm
sudo apt install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0
# Configure autologin
sudo nano /etc/lightdm/lightdm.conf
# Add: autologin-user=hanau
# Set graphical boot
sudo systemctl set-default graphical.target
# Enable service
sudo systemctl enable prayer-kiosk.service
```
## System Information
```bash
# CPU usage during animations
~ 40-50% (WebKitWebProcess)
# GPU status
vcgencmd measure_temp
# temp=57.6'C
vcgencmd get_mem gpu
# gpu=128M
# No throttling
vcgencmd get_throttled
# throttled=0x0
# Memory
free -h
# total used free
# Mem: 7.8Gi 2.1Gi 5.4Gi
# Load average during animations
uptime
# load average: 1.70, 1.26, 0.60
```
## Questions to the Community
1. **Is Raspberry Pi 5 too weak** for such web animations in kiosk mode?
2. **Are there better browser alternatives** than WebKit/Chromium for animations?
3. **Should I switch to native application** (e.g., PyQt/Tkinter) instead of web browser?
4. **Additional GPU/system optimizations** I might have missed?
5. **Hardware-accelerated video overlay** possible for such applications?
6. **Different approach**: Use **Electron** or **Flutter** for better performance?
7. **Video-based solution**: Convert animations to **H.264 video** and use hardware decoder?
The app runs perfectly smooth on desktop PCs, only on the Pi it stutters. Any tips are welcome!
## Additional Information
- **Network**: Local (Flask server on same Pi)
- **Images**: ~500KB-1MB per sponsor image
- **Slideshow speed**: 5-8 seconds per image
- **Text slider**: ~35 seconds for complete run
- **Display resolution**: 1920x1080 @ 60Hz
- **Use case**: Mosque prayer time display (runs 24/7)
## What Works Well
- **Static content** displays perfectly
- **Clock updates** (every second) work smoothly
- **SSE updates** work without issues
- **Auto-boot and auto-start** work reliably
## What Doesn't Work Well
- **CSS transitions** (opacity, transform)
- **Scrolling text** (marquee-style animation)
- **Image slideshow** transitions
- **Any continuous animation** stutters
Has anyone successfully implemented smooth animations on Raspberry Pi 5 in kiosk mode? What's the best approach for such use cases?
Thank you for your help!
---
**Hardware**: Raspberry Pi 5 (8GB)
**OS**: Raspberry Pi OS Lite
**Browser**: WebKit2GTK, Chromium tested
**Issue**: Choppy CSS animations despite GPU optimizations
**Goal**: Smooth 24/7 kiosk display for mosque
Statistics: Posted by TheDaywalker — Thu May 22, 2025 7:53 pm — Replies 0 — Views 23