3 * Admin preview for common desktop, tablet and mobile viewport sizes.
5(function (window, document) {
8 if (!window.dbx || !window.dbx.feature) {
9 console.error("[dbx][viewport] dbx core missing");
13 const dbx = window.dbx;
14 const LIB = "viewport";
15 const PREVIEW_PARAM = "dbx_viewport_preview";
17 "desktop-4k": { label: "Desktop Wide 4K", width: 3840, height: 2160, icon: "bi-display" },
18 "desktop-full-hd": { label: "Desktop Full HD", width: 1920, height: 1080, icon: "bi-display" },
19 "desktop-hd": { label: "Desktop HD", width: 1366, height: 768, icon: "bi-display" },
20 "tablet-portrait": { label: "Tablet hoch", width: 820, height: 1180, icon: "bi-tablet" },
21 "tablet-landscape": { label: "Tablet quer", width: 1180, height: 820, icon: "bi-tablet-landscape" },
22 "mobile-portrait": { label: "Mobile hoch", width: 390, height: 844, icon: "bi-phone" },
23 "mobile-landscape": { label: "Mobile quer", width: 844, height: 390, icon: "bi-phone-landscape" }
27 let currentKey = "desktop-full-hd";
30 function isPreviewPage() {
31 return new URLSearchParams(window.location.search).get(PREVIEW_PARAM) === "1";
34 function previewUrl() {
35 const url = new URL(window.location.href);
36 url.searchParams.set(PREVIEW_PARAM, "1");
37 url.searchParams.delete("dbx_ajax");
38 url.searchParams.delete("dbx_sync");
39 url.searchParams.delete("dbx_nocache");
40 url.searchParams.delete("cachebust");
44 function optionMarkup() {
45 return Object.keys(PRESETS).map(key => {
46 const preset = PRESETS[key];
47 return `<option value="${key}">${preset.label} · ${preset.width} × ${preset.height}</option>`;
52 if (lab && lab.isConnected) return lab;
54 lab = document.createElement("section");
55 lab.className = "dbx-viewport-lab";
57 lab.setAttribute("aria-label", "Viewport-Vorschau");
59 <header class="dbx-viewport-toolbar">
60 <div class="dbx-viewport-toolbar-brand">
61 <i class="bi bi-bounding-box-circles" aria-hidden="true"></i>
62 <span>Viewport-Test</span>
64 <label class="dbx-viewport-profile-select">
65 <span class="visually-hidden">Auflösung</span>
66 <select data-dbx-viewport-select>${optionMarkup()}</select>
68 <span class="dbx-viewport-dimensions" data-dbx-viewport-dimensions></span>
69 <span class="dbx-viewport-scale" data-dbx-viewport-scale></span>
70 <div class="dbx-viewport-toolbar-actions">
71 <button type="button" data-dbx-viewport-rotate title="Hoch- und Querformat tauschen" aria-label="Hoch- und Querformat tauschen">
72 <i class="bi bi-arrow-repeat"></i>
74 <button type="button" data-dbx-viewport-reload title="Vorschau neu laden" aria-label="Vorschau neu laden">
75 <i class="bi bi-arrow-clockwise"></i>
77 <button type="button" data-dbx-viewport-open title="Vorschau in neuem Tab öffnen" aria-label="Vorschau in neuem Tab öffnen">
78 <i class="bi bi-box-arrow-up-right"></i>
80 <button type="button" class="dbx-viewport-close" data-dbx-viewport-close title="Viewport-Test schließen" aria-label="Viewport-Test schließen">
81 <i class="bi bi-x-lg"></i>
85 <div class="dbx-viewport-stage" data-dbx-viewport-stage>
86 <div class="dbx-viewport-device" data-dbx-viewport-device>
87 <iframe data-dbx-viewport-frame scrolling="yes" title="Responsive Vorschau der aktuellen Seite"></iframe>
91 document.body.appendChild(lab);
96 function findPreset(width, height) {
97 return Object.keys(PRESETS).find(key => {
98 const preset = PRESETS[key];
99 return preset.width === width && preset.height === height;
103 function activeDimensions() {
104 const frame = lab && lab.querySelector("[data-dbx-viewport-frame]");
106 width: Number(frame && frame.dataset.viewportWidth) || PRESETS[currentKey].width,
107 height: Number(frame && frame.dataset.viewportHeight) || PRESETS[currentKey].height
112 if (!lab || lab.hidden) return;
114 const stage = lab.querySelector("[data-dbx-viewport-stage]");
115 const device = lab.querySelector("[data-dbx-viewport-device]");
116 const frame = lab.querySelector("[data-dbx-viewport-frame]");
117 const scaleLabel = lab.querySelector("[data-dbx-viewport-scale]");
118 const dimensions = activeDimensions();
119 const rect = stage.getBoundingClientRect();
120 const availableWidth = Math.max(240, rect.width - 42);
121 const availableHeight = Math.max(180, rect.height - 42);
122 const scale = Math.min(1, availableWidth / dimensions.width, availableHeight / dimensions.height);
124 device.style.width = Math.max(1, Math.round(dimensions.width * scale)) + "px";
125 device.style.height = Math.max(1, Math.round(dimensions.height * scale)) + "px";
126 frame.style.width = dimensions.width + "px";
127 frame.style.height = dimensions.height + "px";
128 frame.style.transform = `scale(${scale})`;
129 scaleLabel.textContent = Math.round(scale * 100) + "%";
132 function scheduleFit() {
133 if (resizeFrame) window.cancelAnimationFrame(resizeFrame);
134 resizeFrame = window.requestAnimationFrame(function () {
140 function syncMenuState(key) {
141 document.querySelectorAll("[data-dbx-viewport-preset]").forEach(link => {
142 const active = link.getAttribute("data-dbx-viewport-preset") === key;
143 link.classList.toggle("is-active", active);
144 link.setAttribute("aria-current", active ? "true" : "false");
148 function applyDimensions(width, height, key) {
149 const frame = lab.querySelector("[data-dbx-viewport-frame]");
150 const label = lab.querySelector("[data-dbx-viewport-dimensions]");
151 const select = lab.querySelector("[data-dbx-viewport-select]");
153 frame.dataset.viewportWidth = String(width);
154 frame.dataset.viewportHeight = String(height);
155 label.textContent = width + " × " + height + " px";
157 if (key && PRESETS[key]) {
166 function applyPreset(key) {
167 const preset = PRESETS[key];
169 applyDimensions(preset.width, preset.height, key);
172 function openLab(key) {
173 const root = buildLab();
174 const frame = root.querySelector("[data-dbx-viewport-frame]");
176 document.documentElement.classList.add("dbx-viewport-lab-open");
178 applyPreset(PRESETS[key] ? key : currentKey);
179 if (!frame.getAttribute("src")) frame.setAttribute("src", previewUrl());
183 function closeLab() {
186 document.documentElement.classList.remove("dbx-viewport-lab-open");
189 function rotateLab() {
190 const dimensions = activeDimensions();
191 const rotatedKey = findPreset(dimensions.height, dimensions.width);
192 applyDimensions(dimensions.height, dimensions.width, rotatedKey);
195 function reloadLab() {
196 const frame = lab && lab.querySelector("[data-dbx-viewport-frame]");
199 frame.contentWindow.location.reload();
201 frame.setAttribute("src", previewUrl());
205 function bindLabEvents(root) {
206 root.querySelector("[data-dbx-viewport-select]").addEventListener("change", function () {
207 applyPreset(this.value);
209 root.querySelector("[data-dbx-viewport-rotate]").addEventListener("click", rotateLab);
210 root.querySelector("[data-dbx-viewport-reload]").addEventListener("click", reloadLab);
211 root.querySelector("[data-dbx-viewport-open]").addEventListener("click", function () {
212 const frame = root.querySelector("[data-dbx-viewport-frame]");
213 const link = document.createElement("a");
214 link.href = frame.getAttribute("src") || previewUrl();
215 link.target = "_blank";
216 link.rel = "noopener";
218 document.body.appendChild(link);
222 root.querySelector("[data-dbx-viewport-close]").addEventListener("click", closeLab);
225 function bindMenu(root) {
226 root.querySelectorAll("[data-dbx-viewport-preset]").forEach(link => {
227 link.addEventListener("click", function (event) {
228 event.preventDefault();
229 event.stopPropagation();
230 const menuItem = link.closest(".dbx-menu-item.is-open");
232 menuItem.classList.remove("is-open");
233 const toggle = menuItem.querySelector(":scope > .dbx-menu-link");
234 if (toggle) toggle.setAttribute("aria-expanded", "false");
236 openLab(link.getAttribute("data-dbx-viewport-preset"));
241 function init(root) {
242 if (isPreviewPage()) {
243 document.documentElement.classList.add("dbx-viewport-preview-page");
247 window.addEventListener("resize", scheduleFit, { passive: true });
248 document.addEventListener("keydown", function (event) {
249 if (event.key === "Escape" && lab && !lab.hidden) closeLab();
260 dbx.feature.register(LIB, {
264 ["css", "root", "design/dbxapp/css/c-viewport.css"]