はじめに
結婚式を控えた友人から、披露宴で使えるリアルタイムスライドショーを頼まれました。
ここでいうリアルタイムスライドショーとは、披露宴中に参加者が写真を投稿し、投稿された写真をリアルタイムに表示する仕組みです。
リアルタイムスライドショーには有償のサービスもありますが、個人で作成したものも公開されています。
本記事では、Swiper.jsを使ってリアルタイム感高めのリアルタイムスライドショーをつくりました。
つくったもの
表示例(John Smithさんが写真を2枚投稿して、そのあとさらに3枚投稿した例)
つくりかた
構成とコードを簡単に解説します。
コード詳細や使用方法はGithubを参照してください。
基本的な構成、写真の投稿方法は公開されていたものを参考にさせていただきました。
本記事では、リアルタイム感の高いスライドショー表示になるよう工夫しました。
構成
Dropboxのファイルリクエスト機能を使って写真を投稿してもらい、Dropboxのフォルダに画像を集めます。
Pythonスクリプトはフォルダ内の画像のリストをJSONファイルとして作成し、2秒おきに更新します。
Swiperは画像を自動でスワイプ表示させます。
スワイプ動作時にJSONファイルを確認して、表示画像を追加します。
使用したバージョン
Python 3.7.0
Swiper.js 4.4.6
写真の投稿
写真の投稿にはDropboxのファイルリクエスト機能を使用しました。
披露宴会場では、QRコードを印刷したフォトコンテスト案内を配布しました。
参加者は以下のようなページにQRコードからアクセスして、写真をアップロードできます。
画像リストの更新
投稿された画像のリストは、以下のようなJSONファイルで保存するようにしました。
画像が追加された順に並んでおり、各画像のパスhref
、投稿者の名前title
、時間time-from-epoch
が含まれています。(時間情報time-from-epoch
はリスト作成時の並び替えで使用しており、time
は可読性のために入れているだけです。)
[
{
"href": "d:\\Dropbox\\realtime-slideshow\\DropboxPhoto\\John Smith - IMG02.jpg",
"time": "Sat May 9 00:30:16 2020",
"time-from-epoch": 1588951816.3165922,
"title": "John Smith"
},
{
"href": "d:\\Dropbox\\realtime-slideshow\\DropboxPhoto\\John Smith - IMG01.jpg",
"time": "Sat May 9 00:30:54 2020",
"time-from-epoch": 1588951854.2131982,
"title": "John Smith"
}
]
このJSONファイルは、以下のPythonスクリプトで作成して2秒おきに更新し続けます。
スクリプト実行中は、画像の合計枚数がコンソールに表示されます。
Dropboxのフォルダには容量制約があるので、本番では念のため合計枚数を監視していました。
参考にさせていただいた解説では、縦長の写真が横になってしまう問題が指摘されていました。
この問題は、画像のexif情報を削除することで解決できました。
import os
import json
import codecs
from datetime import date, datetime
import time
import exifmodify
# Convert datetime to charactor
def json_serial(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
# Make image list in the path as json
def json_tree_image(path):
target_json = []
for item in os.listdir(path):
full_path = os.path.join(path,item)
new_hash = {}
new_hash['title'] = item.split(' -',1)[0]
new_hash['href'] = full_path.replace(os.getcwd()+'/','')
new_hash['time'] = time.ctime(os.path.getmtime(full_path))
new_hash['time-from-epoch'] = os.path.getmtime(full_path)
target_json.append(new_hash)
target_json.sort(key=lambda x: -x['time-from-epoch'])
return target_json
if __name__ == '__main__':
path = os.getcwd()+'\DropboxPhoto'
# Json update loop
while True:
list_new = json_tree_image(path)
with codecs.open('image.json','w','utf-8') as f:
dump = json.dumps(list_new,indent=4, ensure_ascii=False, sort_keys=True, default=json_serial)
f.write(dump)
# Remove exif info of image for standardize orientation
exifmodify.remove_exif(path)
time.sleep(2)
print('Item Num: ',len(list_new))
スライドショー
披露宴で写真が投稿されるタイミングを考えると、短時間で多数投稿される時間もあれば、あまり投稿されない時間もありそうです。
画像を1枚ずつ一定間隔で表示すると、同じタイミングで多数投稿された場合にすぐには表示されず、リアルタイム感が薄れてしまいます。
そこで、写真をスワイプさせながら複数枚表示して、(小さくても一瞬でも)投稿されたものは投稿直後に表示されるようにしました。
Swiper.jsを利用して上下2つの横スクロールの画像スライドを用意しました。
上段の大きい写真がswiper1
、下段の小さい写真がswiper2
です。
<head>
<meta charset="UTF-8">
<title>Realtilme Slideshow</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="swiper.min.css">
<script src="swiper.min.js"></script>
</head>
<body>
<!-- 1st Swiper START -->
<div class="swiper-container swiper1">
<div class="swiper-wrapper wrapper1">
<div class="swiper-slide slide1"> <img src="titleimage\slide1.png"></div>
<div class="swiper-slide slide1"> <img src="titleimage\slide2.png"></div>
</div>
<div class="swiper-scrollbar scrollbar1"></div>
</div>
<!-- 2nd Swiper START -->
<div class="swiper-container swiper2">
<div class="swiper-wrapper wrapper2">
<div class="swiper-slide slide2"> <img src="titleimage\slide1.png"></div>
<div class="swiper-slide slide2"> <img src="titleimage\slide2.png"></div>
</div>
<div class="swiper-scrollbar scrollbar2"></div>
</div>
<script src="script.js"></script>
</body>
</html>
各Swiperの動作は、script.jsで指定しています。
autoplay
オプションによって、上段だけが自動でスワイプされます。
スライドの同期はcontroller
オプションでもできるようなのですが、うまく動作しないことがあったため、イベント処理で同期させました。
mySwiper1
が自動で動くと、mySwiper1.on('SlideChange,~)
がイベントを拾って、mySwiper2
をmySwiper1
と同じ位置に動かします。
逆の動作をするmySwiper2.on('SlideChange,~)
は、手動でmySwiper2
を動かしたときにも同期させるために用意しました。
表示を制御するうえでポイントになるのは、以下のオプションです。
-
speed
:スワイプ動作にかかる時間[ms] -
autoplay:
delay
:ひとつのスライドを表示している時間[ms] -
slidesPerView
:画面内に表示させるスライド数
今回はdelay:1000
、speed:4000
なので、1枚の画像を1秒見せて、4秒かけて次の画像に移動する動作になります。
また、slidesPerView
は1.5と8なので、上段は1.5枚分、下段は8枚分が表示されています。
// 1st Swiper
var mySwiper1 = new Swiper('.swiper1', {
effect: 'coverflow',
initialSlide: 0,
slidesPerView: 1.5,
speed: 4000,
slidesPerGroup: 1,
loop: false,
centeredSlides: true,
spaceBetween: 0,
freeMode: false,
scrollbar: {
el: '.scrollbar1',
hide: true,
draggable: true
},
autoplay: {
delay: 1000,
stopOnLastSlide: false,
disableOnInteraction: false,
reverseDirection: false
}
},
});
// 2nd Swiper
var mySwiper2 = new Swiper('.swiper2', {
spaceBetween: 0,
initialSlide: 0,
slidesPerView: 8,
slidesPerGroup: 1,
loop: false,
centeredSlides: true,
spaceBetween: 3,
freeMode: false,
scrollbar: {
el: '.scrollbar2',
hide: true,
draggable: true
}
});
// Synchronizatiion of 2 swipers
mySwiper1.on('slideChange', function () {
mySwiper2.slideTo(mySwiper1.realIndex, 1000, false); //(index, speed(ms), runCallbacks)
});
mySwiper2.on('slideChange', function () {
mySwiper1.slideTo(mySwiper2.realIndex, 1000, false); //(index, speed(ms), runCallbacks)
});
イベント処理mySwiper1.on('autoplay',~)
によって、スライド移動時(5秒おき)にJSONファイルを確認します。
画像が追加されていたら、スライドを追加して、最新の画像(左端スライド)に移動させます。
画像枚数が60枚を超えたら、古い画像のスライドを削除します。
// image update
var slide_max = 62;
var jsonfile1 = 'image.json';
var jsonimage = [];
var jsonimage_disp = [];
mySwiper1.on('autoplay', function () {
// Read json
httpObj1 = new XMLHttpRequest();
httpObj1.open("get", jsonfile1, true);
httpObj1.onload = function(){
jsonimage = JSON.parse(httpObj1.responseText);
};
httpObj1.send(null);
// Add new image
var num_new = jsonimage.length - jsonimage_disp.length;
if (num_new > 0){
for (var i=0; i<num_new; i++){
console.log(jsonimage[num_new - i - 1].href);
mySwiper1.prependSlide('<div class="swiper-slide slide1"><img src="' + jsonimage[num_new - i - 1].href + '"><div class="slidetitle"><p>Posted by <b>' + jsonimage[num_new - i - 1].title + '</b></p></div></div>');
mySwiper2.prependSlide('<div class="swiper-slide slide2"><img src="' + jsonimage[num_new - i - 1].href + '"></div>');
}
if ( mySwiper1.slides.length > slide_max) {
var remove_num = mySwiper1.slides.length - slide_max;
for (var i=0; i< remove_num ; i++){
mySwiper1.removeSlide(mySwiper1.slides.length - 3);
mySwiper2.removeSlide(mySwiper2.slides.length - 3);
}
}
mySwiper1.update();
mySwiper2.update();
mySwiper1.slideTo(0, 400 * mySwiper1.realIndex, false); //(index, speed(ms), runCallbacks)
mySwiper2.slideTo(0, 400 * mySwiper2.realIndex, false); //(index, speed(ms), runCallbacks)
jsonimage_disp = jsonimage;
}
});
画像枚数が100枚に迫るほど多くなると、スワイプ動作が重く、コマ落ちのようになってきます。
特に、しばらく投稿されていなかったあとに投稿されると、ぐわーっと左端スライドに戻るのですが、このときに表示がカクカクします。
今回は、祝宴の場では5分間でループしてても誰も気づかないだろうと考え、60枚を表示上限に設定しました。
最後にQキーを押すと終了画像が表示されるようにしました。
また、念のため、スペースバーを押すと「しばらくお待ちください画像」が表示されるようにしておきました。
document.onkeydown = function keydown() {
if(event.keyCode == 32){ // Spacekey
location.href = 'file:titleimage/emergency.png'
}
else if(event.keyCode == 81){ // Q key
location.href = 'file:titleimage/final.png'
}
};
おわりに
JavaScript初心者ですが、Swiper.jsを使うことで比較的簡単にそれなりの表示が作れたと思います。
ただ、画像スライドがときどき思ったように動かせないことがあったので、一つの動作例として参考になる部分があれば幸いです。
参考サイト
- knight_photo_contest(AngularJSとDropboxによるリアルタイムスライドショー)
- Python3でJPEG画像のExifを取得し、回転方向を修正する
- あるディレクトリ以下のファイル一覧をJSONで出力する
- Swiper.js超解説(各種ナビゲーションカスタマイズ編)
- swiper.js使ってみたからそのオプションについて(v4.1.6)