Qt offers many alternatives for what you want (They are not complete solutions since you do not clearly indicate what you require):
The implementation of the mouse-over effect will not be implemented because I am not an expert in frontend, but I will focus on communication between the parties.
To communicate Python information to JS, you can do it with the runJavaScript() method of QWebEnginePage and/or with QWebChannel, and the reverse part with QWebChannel (I don't rule out the idea that QWebEngineUrlRequestInterceptor could be an alternative solution but in this case the previous solutions they are simpler). So in this case I will use QWebChannel.
The idea is to register a QObject that sends the information through signals (in this case JSON), by the side of javascript parsing the JSON and creating dynamic HTML, then before any event such as the click call a slot of the QObject.
Considering the above, the solution is:
├── index.html
├── index.js
└── main.py
import json
from PySide2 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, QtWebChannel
class GalleryManager(QtCore.QObject):
dataChanged = QtCore.Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._data = []
self._is_loaded = False
@QtCore.Slot(str)
def make_action(self, identifier):
print(identifier)
@QtCore.Slot()
def initialize(self):
self._is_loaded = True
self.send_data()
def send_data(self):
if self._is_loaded:
self.dataChanged.emit(json.dumps(self._data))
@property
def data(self):
return self._data
@data.setter
def data(self, d):
self._data = d
self.send_data()
if __name__ == "__main__":
import os
import sys
# sys.argv.append("--remote-debugging-port=8000")
app = QtWidgets.QApplication(sys.argv)
current_dir = os.path.dirname(os.path.realpath(__file__))
view = QtWebEngineWidgets.QWebEngineView()
channel = QtWebChannel.QWebChannel(view)
gallery_manager = GalleryManager(view)
channel.registerObject("gallery_manager", gallery_manager)
view.page().setWebChannel(channel)
def on_load_finished(ok):
if not ok:
return
data = []
for i, path in enumerate(
(
"https://source.unsplash.com/pWkk7iiCoDM/400x300",
"https://source.unsplash.com/aob0ukAYfuI/400x300",
"https://source.unsplash.com/EUfxH-pze7s/400x300",
"https://source.unsplash.com/M185_qYH8vg/400x300",
"https://source.unsplash.com/sesveuG_rNo/400x300",
"https://source.unsplash.com/AvhMzHwiE_0/400x300",
"https://source.unsplash.com/2gYsZUmockw/400x300",
"https://source.unsplash.com/EMSDtjVHdQ8/400x300",
"https://source.unsplash.com/8mUEy0ABdNE/400x300",
"https://source.unsplash.com/G9Rfc1qccH4/400x300",
"https://source.unsplash.com/aJeH0KcFkuc/400x300",
"https://source.unsplash.com/p2TQ-3Bh3Oo/400x300",
)
):
d = {"url": path, "identifier": "id-{}".format(i)}
data.append(d)
gallery_manager.data = data
view.loadFinished.connect(on_load_finished)
filename = os.path.join(current_dir, "index.html")
view.load(QtCore.QUrl.fromLocalFile(filename))
view.resize(640, 480)
view.show()
sys.exit(app.exec_())
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="index.js"> </script>
</head>
<body>
<div class="container">
<h1 class="font-weight-light text-center text-lg-left mt-4 mb-0">Thumbnail Gallery</h1>
<hr class="mt-2 mb-5">
<div id="container" class="row text-center text-lg-left">
</div>
</div>
</body>
</html>
var gallery_manager = null;
new QWebChannel(qt.webChannelTransport, function (channel) {
gallery_manager = channel.objects.gallery_manager;
gallery_manager.dataChanged.connect(populate_gallery);
gallery_manager.initialize();
});
function populate_gallery(data) {
const container = document.getElementById('container');
// clear
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// parse json
var d = JSON.parse(data);
// fill data
for (const e of d) {
var identifier = e["identifier"];
var url = e["url"];
var div_element = create_div(identifier, url)
container.appendChild(div_element);
}
}
function create_div(identifier, url){
var html = `
<div class="d-block mb-4 h-100">
<img class="img-fluid img-thumbnail" src="${url}" alt="">
</div>
`
var div_element = document.createElement("div");
div_element.className = "col-lg-3 col-md-4 col-6"
div_element.innerHTML = html;
div_element.addEventListener('click', function (event) {
gallery_manager.make_action(identifier);
});
return div_element;
}