图片列表预览
Tätä skriptiä ei tulisi asentaa suoraan. Se on kirjasto muita skriptejä varten sisällytettäväksi metadirektiivillä // @require https://update.sleazyfork.org/scripts/546691/1758519/GM_Preview.js.
(function () {
class GM_Preview {
options;
$mask;
$thumbs;
$img;
$index;
$prev;
$next;
#srcList = [];
#index = 0;
#rotate = 0;
#scale = 1;
constructor(
options = {
showThumbs: true,
},
) {
this.options = options;
this.#initStyle();
this.#initElements();
this.#initImg();
this.#initThumbs();
this.#initActions();
this.#initZoom();
this.#initMove();
}
show(srcList, index) {
this.#srcList = srcList;
this.#index = index;
if (this.options.showThumbs) {
this.$thumbs.innerHTML = this.#srcList
.map((src, i) => `<img src="${src}" data-index="${i}">`)
.join("");
}
this.#update();
document.body.parentElement.classList.add("GM_Preview");
window.addEventListener("keydown", (this.#onkeydown = this.onkeydown.bind(this)));
}
show_prev() {
this.#index = this.#index > 0 ? this.#index - 1 : this.#srcList.length - 1;
this.#update();
}
show_next() {
this.#index = this.#index < this.#srcList.length - 1 ? this.#index + 1 : 0;
this.#update();
}
#update() {
this.$img.classList.remove("anime");
this.resetTransform();
this.$img.src = this.#srcList[this.#index];
if (this.options.showThumbs) {
this.$thumbs.querySelector(".active")?.classList.remove("active");
this.$thumbs.children[this.#index].classList.add("active");
this.$thumbs.children[this.#index].scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest",
});
}
this.$index.textContent = `${this.#index + 1} / ${this.#srcList.length}`;
}
close() {
document.body.parentElement.classList.remove("GM_Preview");
window.removeEventListener("keydown", this.#onkeydown);
this.$img.src = "#";
this.resetTransform();
}
#onkeydown;
onkeydown(e) {
switch (e.code) {
case "ArrowLeft":
this.show_prev();
break;
case "ArrowRight":
this.show_next();
break;
case "Escape":
this.close();
break;
}
}
open() {
window.open(this.#srcList[this.#index]);
}
set rotate(v) {
this.$img.style.rotate = `${(this.#rotate = v)}deg`;
}
get rotate() {
return this.#rotate;
}
rotate_left() {
this.rotate -= 90;
}
rotate_right() {
this.rotate += 90;
}
set scale(v) {
this.$img.style.scale = `${(this.#scale = v)}`;
}
get scale() {
return this.#scale;
}
zoom_out() {
this.scale *= 0.8;
}
zoom_in() {
this.scale *= 1.2;
}
resetTransform() {
this.rotate = 0;
this.scale = 1;
this.$img.style.top = "";
this.$img.style.left = "";
}
#initThumbs() {
if (this.options.showThumbs) {
this.$thumbs.onmousedown = (e) => e.stopPropagation();
this.$thumbs.onmousewheel = (e) => {
this.$thumbs.scrollLeft += e.deltaY;
e.stopPropagation();
};
}
}
#initImg() {
this.$img.onload = () => {
const rect = this.$img.getBoundingClientRect();
const top =
(window.innerHeight - (this.options.showThumbs ? 196 : 70) - rect.height) / 2 + 60;
this.$img.style.top = `${top}px`;
this.$img.style.left = `${rect.x}px`;
this.$img.classList.add("anime");
};
}
#initActions() {
this.$mask.onclick = (e) => {
if (e.target.dataset.action) {
this[e.target.dataset.action]();
} else if (e.target.dataset.index) {
this.#index = parseInt(e.target.dataset.index);
this.#update();
}
};
}
#initZoom() {
this.$mask.onmousewheel = (e) => {
if (e.deltaY < 0) this.zoom_in();
else this.zoom_out();
};
}
#initMove() {
let cache = { px: 0, py: 0, ex: 0, ey: 0 };
this.$mask.onmousedown = (e) => {
cache = {
ex: e.x,
ey: e.y,
px: parseInt(this.$img.style.left),
py: parseInt(this.$img.style.top),
};
window.addEventListener("mousemove", onmousemove);
window.addEventListener("mouseup", onmouseup);
};
const onmousemove = (e) => {
this.$img.style.left = `${cache.px + e.x - cache.ex}px`;
this.$img.style.top = `${cache.py + e.y - cache.ey}px`;
};
const onmouseup = () => {
window.removeEventListener("mousemove", onmousemove);
window.removeEventListener("mouseup", onmouseup);
};
}
#initElements() {
const div = document.createElement("div");
div.id = "GM_Preview_Mask";
div.innerHTML = `
<img id="GM_Preview_Img">
<span data-action="close"><i class="iconfont icon-close"></i></span>
<span data-action="show_prev"><i class="iconfont icon-left"></i></span>
<span data-action="show_next"><i class="iconfont icon-right"></i></span>
<div id="GM_Preview_Thumbs"></div>
<div id="GM_Preview_Index"></div>
<div id="GM_Preview_Actions">
<span data-action="open"><i class="iconfont icon-open"></i></span>
<span data-action="rotate_left"><i class="iconfont icon-rotate-left"></i></span>
<span data-action="rotate_right"><i class="iconfont icon-rotate-right"></i></span>
<span data-action="zoom_out"><i class="iconfont icon-zoom-out"></i></span>
<span data-action="zoom_in"><i class="iconfont icon-zoom-in"></i></span>
</div>
`;
document.body.appendChild(div);
this.$mask = div;
this.$thumbs = div.querySelector("#GM_Preview_Thumbs");
if (!this.options.showThumbs) this.$thumbs.remove();
this.$img = div.querySelector("#GM_Preview_Img");
this.$index = div.querySelector("#GM_Preview_Index");
this.$prev = div.querySelector('[data-action="show_prev"]');
this.$next = div.querySelector('[data-action="show_next"]');
}
#initStyle() {
const link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = "//at.alicdn.com/t/c/font_5127909_7z8tc3ebb1t.css";
document.head.appendChild(link);
const style = document.createElement("style");
style.innerHTML = `
#GM_Preview_Mask {
& * {
user-select: none;
-webkit-user-drag: none;
}
z-index: 1001;
display: none;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
overflow: hidden;
cursor: grab;
&:active {
cursor: grabbing;
}
& #GM_Preview_Img {
position: absolute;
object-fit: contain;
max-width: calc(100vw - 120px);
max-height: calc(100vh - ${this.options.showThumbs ? "196px" : "70px"});
border: 0.5px solid #ccc;
pointer-events: none;
&.anime {
transition: rotate 0.3s, scale 0.3s;
}
}
& #GM_Preview_Thumbs {
position: absolute;
bottom: 10px;
left: 10px;
width: calc(100vw - 20px);
white-space: nowrap;
text-align: center;
overflow: auto hidden;
scrollbar-color: #ffffff rgba(0, 0, 0, 0);
cursor: default;
& img {
display: inline-block;
margin: 0;
width: 100px;
height: 100px;
object-fit: contain;
background-color: rgba(0, 0, 0, 0.8);
cursor: pointer;
border: 3px solid transparent;
transition: border-color 0.15s;
& + img {
margin-left: 10px;
}
&:hover {
border-color: #409EFF;
}
&.active {
border-color: #F56C6C !important;
}
}
}
& [data-action] {
border-radius: 4px;
padding: 4px;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
opacity: 0.5;
transition: all 0.3s;
display: inline-block;
width: 32px;
height: 32px;
text-align: center;
align-content: center;
& > i {
pointer-events: none;
font-size: 24px;
}
&:hover {
opacity: 1;
}
&.disabled {
pointer-events: none;
opacity: 0.2;
}
}
& [data-action="close"] {
position: absolute;
right: 10px;
top: 10px;
color: #F56C6C;
font-weight: bold;
border-radius: 50%;
}
& [data-action="show_prev"] {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
}
& [data-action="show_next"] {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
}
& #GM_Preview_Index {
position: fixed;
left: 50%;
transform: translateX(-50%);
top: 10px;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 4px;
padding: 4px 8px;
font-size: 16px;
opacity: 0.5;
pointer-events: none;
}
& #GM_Preview_Actions {
position: absolute;
left: 10px;
top: 10px;
}
}
html.GM_Preview {
overflow: hidden;
& #GM_Preview_Mask {
display: flex;
align-items: center;
justify-content: center;
}
}
`;
document.head.appendChild(style);
}
}
window.GM_Preview = GM_Preview;
})();