snorpeys bloghttps://snorpey.codes/deEin Blog ber kreatives Coden und Webentwicklung2023-10-24T22:43:24+02:00Georg Fischerhi@snorpey.codeshttps://snorpey.codesEigene Events in JavaScripthttps://snorpey.codes/de/artikel/eigene-events-in-javascriptGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2023-10-24T22:40:00+02:00TIL: Custom Events in Javascript<div>
<p>Heute habe ich gelernt, dass man in JavaScript sehr einfach eigene Events auslösen kann. Funktioniert so nur im DOM, ist aber sehr praktisch, zum Beispiel um einfach Daten zwischen voneinander unanhängigen Skripten auszutauschen.</p>
<p>In über das <code>event.detail</code> Objekt kann man beliebige Daten verschicken.</p></div><figure>
<pre><code><span>const</span> event = <span>new</span> CustomEvent(<span>"my-custom-event"</span>, {
<span>detail</span>: { <span>data</span>: { <span>hello</span>: <span>"world"</span> } },
});
<span>document</span>.dispatchEvent(event);
<span>// Elsewhere in the code, or maybe even in a separate script:</span>
<span>document</span>.addEventListener(<span>"my-custom-event"</span>, (event) => {
<span>console</span>.log(<span>"a thing happened"</span>, event.detail);
});
</code></pre>
</figure><div>
<p>Eine simple Demo (mit einem mehr oder weniger praktischen Beispiel):</p></div><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>JavaScript Custom Event Beispiel</a>
</div>
<figcaption>
<p>JavaScript Custom Event Beispiel</p> <ul>
<li>
<a href="https://snorpey.codes/public/artikel/eigene-events-in-javascript/embeds/customEvent.html">customEvent</a>
</li>
</ul>
</figcaption>
</figure>
</div>Kirby-Sprachumschalterhttps://snorpey.codes/de/artikel/kirby-sprachumschalterGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2023-04-18T19:45:00+02:00Einen Sprachumschalter für Kirby CMS bauen<div>
<p>Ich versuche, möglichst alle Inhalte meiner Website mehrsprachig anzubieten. Dabei helfen mir die Übersetzungs- und Internationalisierungsschnittstellen meines Content Management Systems Kirby. Sie machen es einfach, mehrsprachige Inhalte zu erstellen.</p>
<p>Kürzlich stieß ich jedoch auf ein kleines Problem, dessen Lösung mehr Zeit in Anspruch nahm, als ich erwartet hatte.</p>
<p>Es geht um die Einbindung des Sprachumschalters, der sich am Ende jeder Seite befindet und einen Link zur aktuellen Seite in einer anderen Sprachversion enthält:</p>
<blockquote>
<p>This page is also available in <a href="https://snorpey.codes/en/articles/language-switch-in-kirby-cms">English</a>.</p>
</blockquote>
<p>Dazu habe ich zunächst für die Sprachen, die ich unterstützen möchte (in meinem Fall Deutsch und Englisch) einen neuen Eintrag in der entsprechenden Übersetzungsdatei unter <code>site/language/$lang_code.php</code> eingefügt:</p></div><figure>
<pre><code><span><?php</span>
<span>return</span> [
<span>'code'</span> => <span>'de'</span>,
<span>'default'</span> => <span>true</span>,
<span>'direction'</span> => <span>'ltr'</span>,
<span>'locale'</span> => <span>'de_DE.utf-8'</span>,
<span>'name'</span> => <span>'Deutsch'</span>,
<span>'translations'</span> => [
<span>'footer_translated'</span> => <span>'Diese Seite gibts auch auf <a href="{language_url}">{language_name}</a>'</span>,
]
];
</code></pre>
</figure><figure>
<pre><code><span><?php</span>
<span>return</span> [
<span>'code'</span> => <span>'en'</span>,
<span>'default'</span> => <span>false</span>,
<span>'direction'</span> => <span>'ltr'</span>,
<span>'locale'</span> => <span>'en_US.utf-8'</span>,
<span>'name'</span> => <span>'English'</span>,
<span>'translations'</span> => [
<span>'footer_translated'</span> => <span>'This page is also available in <a href="{language_url}">{language_name}</a>.'</span>,
]
];
</code></pre>
</figure><div>
<p>Als nächstes habe ich das folgende Snippet in der Datei <code>site/snippets/language_switch.php</code> erstellt:</p></div><figure>
<pre><code><span><?php</span>
<span>use</span> <span>Kirby</span>\<span>Toolkit</span>\<span>I18n</span>;
$current_language = $kirby->language();
<span>foreach</span> ($kirby->languages() <span>as</span> $language) {
<span>if</span> ($language !== $current_language) {
$url_for_target_language = $page->url($language->code());
<span>if</span> (!<span>empty</span>($_GET)) {
$query_string = http_build_query($_GET);
<span>if</span> (strlen($query_string) > <span>0</span>) {
$url_for_target_language = $url_for_target_language . <span>'?'</span> . $query_string;
}
}
<span>?></span>
<p lang=<span>"<?= $language->code() ?>"</span>>
<span><?</span>= I18n::template(
<span>'footer_translated'</span>,
<span>null</span>,
[
<span>'language_url'</span> => $url_for_target_language,
<span>'language_name'</span> => $language->name()
],
$language->code()
) <span>?></span>
</p>
<span><?php</span>
}
}
<span>?></span></code></pre>
</figure><div>
<p>Hier werden einige interessante Kirby-Funktionen miteinander verknüpft:</p>
<ul>
<li>In einer Schleife werden zunächst alle Sprachen durchlaufen, die in Kirby angelegt wurden, wobei die aktuelle Sprache übersprungen wird.</li>
<li>Als nächstes wird die URL der aktuellen Seite in einer anderen Sprache gesucht. Diese erhalten wir, indem wir die Hilfsfunktion <code>$page->url()</code> mit einem Parameter aufrufen und ihr die Zielsprache übergeben: <code>$page->url($language->code())</code>.</li>
<li>Bei Bedarf werden auch alle <code>$_GET</code>-Anfrageparameter an die neue URL durchgereicht.</li>
<li>Wir verwenden das <code>lang</code>-Attribut, um dem Browser mitzuteilen, dass der Text in einer anderen Sprache als der Rest des Dokuments geschrieben ist. Dabei hilft uns die Hilfsfunktion <code>$language->code()</code>,</li>
<li>Schließlich verwenden wir die Funktion <code>I18n::template()</code>, um den übersetzten Text in der Zielsprache auszugeben. Zusätzlich ersetzen wir dabei auch den <code>{language_name}</code> durch den Namen der Sprache und <code>{language_name}</code> durch die URL der Sprachversion. Die Standardfunktion <code>t()</code>, die ich sonst zum Übersetzten von Template-Strings verwende, ermöglicht es leider nicht, auch Ersetzungen vorzunehmen.</li>
</ul>
<p>Das Snippet wird dann in allen meinen Templates per <code>snippet('language_switch');</code> eingesetzt.</p></div>Die svh Einheit in CSShttps://snorpey.codes/de/artikel/die-svh-einheit-in-cssGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2023-03-07T18:40:00+01:00Die svh-Einheit in CSS ist sehr hilfreich bei der Entwicklung mobiler Webseiten.<div>
<p>Ich bin neulich auf ein CSS-Feature aufmerksam geworden, dass mir die Entwicklung von Webseiten für mobile Geräte sehr vereinfacht:</p>
<p>Die <code>svh</code> Größeneinheit funktioniert sehr ähnlich wie die <code>vh</code> Einheit und gibt ermöglicht es, die Größe eines Elements in Relation zu der Größe des Browserfensters zu setzen.</p>
<p>So ist zum Beispiel ein Element mit einer Stilregel von <code>height: 100vh</code> genauso hoch wie das Browserfenster. Die Stilregel <code>height: 100svh</code> funktioniert dabei genauso, und resultiert dabei in Desktop-Browsern im gleichen Ergebnis.</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-80x.webp 80w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-120x.webp 120w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-240x.webp 240w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-320x.webp 320w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-480x.webp 480w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-600x.webp 600w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-720x.webp 720w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-800x.webp 800w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-880x.webp 880w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-920x.webp 920w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-80x.avif 80w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-120x.avif 120w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-240x.avif 240w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-320x.avif 320w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-480x.avif 480w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-600x.avif 600w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-720x.avif 720w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-800x.avif 800w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-880x.avif 880w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-920x.avif 920w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/die-svh-einheit-in-css/ebd98bb9b2-1681853451/svh-example-desktop.png" alt="" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
<figcaption>
<p>Im Desktop-Browser werden beide Elemente gleich hoch angezeigt, ein Scrollbar ist nicht zu sehen</p> </figcaption>
</figure>
</div><div>
<p>Interessant wird es, wenn man sich das Ergebnis auf mobilen Browsern ansieht: Hier wird die zur Verfügung stehende Fläche anders berechnet: Da die Benutzeroberflächen von mobilen Browsern teilweise dynamische Elemente haben können, die z.B. beim Scrollen verschwinden, gibt es einen Unterschied zwischen <code>height: 100vh</code> und <code>height: 100svh</code>:</p>
<p>Für die Berechnung von <code>height: 100vh</code> werden Flächen, die vom Browserinterface verdeckt werden mitgezählt, während bei Elementen mit der Höheangabe von <code>height: 100svh</code> nur die nicht verdeckten bzw. sichtbaren Bereiche mitgezählt werden.</p>
<p>Elemente, die die komplette Bildschirmhöhe ausfüllen sollen erzeugen mit <code>height: 100vh</code> auf mobilen Geräten einen Scrollbar, während bei der Angabe von <code>height: 100svh</code> kein Scrollbar erzeugt wird.</p></div><div>
<p>Diese Demo kannst du auch <a href="https://snorpey.codes/public/artikel/die-svh-einheit-in-css/svh/embeds/svh-example.html">selbst ausprobieren</a>.</p>
<p>Laut der Webseite <a href="https://caniuse.com/viewport-unit-variants">Can I Use</a> wird <code>svh</code><br>
(sowie weitere Einheiten wie <code>svw</code>) in allen gängigen Browsern unterstützt.</p></div><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>Die svh Einheit in CSS demo</a>
</div>
<figcaption>
<ul>
<li>
<a href="https://snorpey.codes/public/artikel/die-svh-einheit-in-css/svh/embeds/svh-example.html">svh-example</a>
</li>
</ul>
</figcaption>
</figure>
</div>Javascript Promises in Reihe ausführenhttps://snorpey.codes/de/artikel/javascript-promises-in-reihe-ausfuehrenGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2023-02-04T17:50:00+01:00Wie führt man in JavaScript mehrere Promises in Reihe aus? Da mit diesem Problem schon einige Male konfrontiert war, habe ich dazu meine Lösung dokumentiert.<div>
<p>Ab und zu stehe ich vor dem Problem, in JavaScript mehrere Promises in einer festgelegten Reihenfolge ausführen zu müssen.</p>
<p>Anders als bei parallel auszuführendenden Promises (via <code>Promise.all()</code>) gibt es meines Wissens nach dafür keine Methode am <code>Promise</code>-Objekt. Diese Funktionalität muss man sich also selbst zusammenbauen.</p>
<p>So habe ich dieses Problem für mich gelöst:</p></div><figure>
<pre><code><span>// An example of long running funtion returning a promise.</span>
<span>// for demonstration purposes we just wrap a promise around setTimeout.</span>
<span><span>function</span> <span>longRunningFunctionReturningAPromise</span>(<span>parameter</span>) </span>{
<span>const</span> taskDuration = <span>Math</span>.floor(<span>Math</span>.random() * <span>110</span>);
<span>return</span> <span>new</span> <span>Promise</span>(<span>(<span>resolve, reject</span>) =></span> {
<span>const</span> calculationResult = <span>Math</span>.sin(parameter * parameter) * <span>2</span>;
<span>if</span> (parameter > <span>3</span>) {
<span>// Example for an error.</span>
reject(<span>new</span> <span>Error</span>(<span>`Parameters greater than 3 are not allowed.`</span>));
} <span>else</span> {
setTimeout(<span><span>()</span> =></span> {
resolve(calculationResult);
}, taskDuration);
}
});
}
<span>// Create a promise sequence from an array of tasks.</span>
<span><span>function</span> <span>createSequence</span>(<span>tasks</span>) </span>{
<span>let</span> sequenceResults = [];
<span>// The initial sequence Promise object:</span>
<span>// Promise.resolve() returns a Promise. It also is "Thenable" so we</span>
<span>// can append promises at the end of it by using sequence.then(...)</span>
<span>let</span> sequence = <span>Promise</span>.resolve();
<span>// Append all tasks to the promise</span>
<span>// and execute them only after the previous task has finished.</span>
tasks.forEach(<span>(<span>task, taskIndex</span>) =></span> {
sequence = sequence
.then(<span><span>()</span> =></span> task())
.then(<span><span>taskResult</span> =></span> {
<span>// add the result of the task to the results array</span>
sequenceResults[taskIndex] = taskResult;
});
});
<span>// At the end of the sequence, return the sequence resutlts.</span>
<span>return</span> sequence.then(<span><span>()</span> =></span> sequenceResults);
}
<span>// Create a list of tasks. Note: we wrap each task in a separate anonymous function.</span>
<span>// This is important so we can invoke the tasks in order at a later point in time</span>
<span>// (instead of them being invoked immediately on creation)</span>
<span>const</span> tasks = [
<span><span>()</span> =></span> longRunningFunctionReturningAPromise(<span>1</span>),
() => longRunningFunctionReturningAPromise(<span>2</span>),
() => longRunningFunctionReturningAPromise(<span>3</span>),
];
<span>// Start a sequence that completes</span>
createSequence(tasks).then(
<span><span>sequenceResults</span> =></span> <span>console</span>.log(<span>'Sequence completed:'</span>, sequenceResults),
error => <span>console</span>.log(<span>'Sequence failed:'</span>, error)
);
<span>const</span> tasksThatWillFail = [
<span><span>()</span> =></span> longRunningFunctionReturningAPromise(<span>2</span>),
() => longRunningFunctionReturningAPromise(<span>3</span>),
() => longRunningFunctionReturningAPromise(<span>4</span>),
];
<span>// Start a sequence that will throw an error</span>
createSequence(tasksThatWillFail).then(
<span><span>sequenceResults</span> =></span> <span>console</span>.log(<span>'Sequence completed:'</span>, sequenceResults),
error => <span>console</span>.log(<span>'Sequence failed:'</span>, error)
);
</code></pre>
</figure><div>
<p>Folgendes war mit bei dieser Lösung wichtig:</p>
<ul>
<li>Alle <code>Promise</code>s werden in eine <em>anonyme Funktion</em> verpackt, so dass sie erst erst ausgeführt werden, wenn das vorhergehende Promise aufgelöst wurde.</li>
<li>Sobald ein Fehler auftritt und ein <code>Promise</code> nicht aufgelöst (<code>resolved</code>) werden kann, wird die Sequenz angehalten, keine weiteren <code>Promise</code>s ausgeführt und die aufgetretenen Fehler propagiert.</li>
</ul></div>Browser Fullscreen APIshttps://snorpey.codes/de/artikel/browser-fullscreen-apisGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2023-01-16T22:00:00+01:00Eine Übersicht über alle wichtigen JavaScript-Funktionen die man benötigt, um mit dem Vollbildmodus des Browsers zu interagieren.<div>
<p>Obwohl ich ab und and die Browser-Fullscreen-APIs in einigen meiner Projekte verwendet habe, waren mir einige Details noch nicht bekannt.</p>
<p>Deshalb habe ich im Folgenden einige nützliche Code-Schnipsel zusammengestellt.</p>
<h2><a href="https://snorpey.codes/de#geprefixte-methoden-und-objekte">Geprefixte Methoden und Objekte</a></h2>
<p>Laut <a href="https://caniuse.com/fullscreen">caniuse</a> ist die Vollbild-Schnittstelle in <em>Safari</em> noch geprefixed. Hier muss jeweils im Methodennamen noch <code>webkit</code> vornangestellt werden; so muss man beispielsweise in Safari: <code>element.webkitRequestFullscreen()</code> statt <code>element.requestFullscreen()</code> aufrufen.</p>
<p>Ich verwende folgende Hilfsfunkiton, um in jedem Browser die richtige Methode aufzurufen, bzw. auf das richtige Objekt zuzugreifen:</p></div><figure>
<pre><code><span>const</span> element = <span>document</span>.querySelector(<span>'#my-fullscreen-element'</span>);
<span>// default</span>
element.requestFullscreen();
<span>// hide browser navigation UI</span>
element.requestFullscreen({ <span>navigationUI</span>: <span>'hide'</span> });
<span>// show browser navigation UI</span>
element.requestFullscreen({ <span>navigationUI</span>: <span>'show'</span> });
<span>// requestFullscreen returns a promise</span>
element.requestFullscreen().then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'you are now in fullscreen mode'</span>);
});
<span>// cross-browser method</span>
<span>const</span> requestFullScreen = getPrefixedElementMethod(
element,
<span>'requestFullScreen'</span>
);
requestFullScreen();
requestFullScreen({ <span>navigationUI</span>: <span>'hide'</span> }).then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'opened fullscreen'</span>);
});
</code></pre>
</figure><div>
<h2>Element im Vollbildmodus öffnen</h2>
<p>Mit der Methode <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen"><code>requestFullscreen()</code></a> kann man ein Element im Vollbildmodus öffnen.</p>
<p>Die Methode hat einen optionalen <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen#parameters"><code>options</code>-Parameter</a>, mit dem man einstellen kann ob Navigationselemente des Browsers angezeigt werden oder nicht (Ausnahmen: <em>Firefox</em> und <em>Safari</em>).</p>
<p>Die Methode gibt ein <code>Promise</code> zurück, so dass man auf die Umschaltung in den Vollbildmodus reagieren kann, um z.B. nach der Vollbild-Umschaltung Code auszuführen (Ausnahme: <em>Safari</em>).</p></div><figure>
<pre><code><span>const</span> element = <span>document</span>.querySelector(<span>'#my-fullscreen-element'</span>);
<span>// default</span>
element.requestFullscreen();
<span>// hide browser navigation UI</span>
element.requestFullscreen({ <span>navigationUI</span>: <span>'hide'</span> });
<span>// show browser navigation UI</span>
element.requestFullscreen({ <span>navigationUI</span>: <span>'show'</span> });
<span>// requestFullscreen returns a promise</span>
element.requestFullscreen().then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'you are now in fullscreen mode'</span>);
});
<span>// cross-browser method</span>
<span>const</span> requestFullScreen = getPrefixedElementMethod(
element,
<span>'requestFullScreen'</span>
);
requestFullScreen();
requestFullScreen({ <span>navigationUI</span>: <span>'hide'</span> }).then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'opened fullscreen'</span>);
});
</code></pre>
</figure><div>
<h2>Vollbildmodus schließen</h2>
<p>Die Methode <code>document.exitFullscreen()</code> schließt den Vollbildmodus. Wichtig hier: <code>exitFullscreen</code> ist eine Methode des <code>document</code>-Objekts, nicht des <code>element</code> Objekts.</p>
<p>Auch <code>document.exitFullscreen()</code> gibt ein <code>Promise</code> zurück (Ausnahme: <em>Safari</em>).</p></div><figure>
<pre><code><span>// default</span>
<span>document</span>.exitFullscreen();
<span>// exitPromise returns a Promise</span>
<span>document</span>.exitFullscreen().then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'full screen was closed'</span>);
});
<span>// cross-browser method</span>
<span>const</span> exitFullScreen = getPrefixedElementMethod(<span>document</span>, <span>'exitFullScreen'</span>);
exitFullScreen();
exitFullScreen().then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'closed fullscreen'</span>);
});
</code></pre>
</figure><div>
<h2><a href="https://snorpey.codes/de#vollbildmodus-erkennen">Vollbildmodus erkennen</a></h2>
<p>Das Objekt <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenElement"><code>document.fullscreenElement</code></a> enthält eine Referenz auf das Element, das im Vollbildmodus geöffnet wurde. In Safari ist dieses Objekt geprefixed: <code>document.webkitFullscreenElement</code> (Ausnahme <em>Safari auf dem iPhone</em>: hier existiert das Objekt nicht)</p></div><figure>
<pre><code><span>// detect fullscreen</span>
<span>if</span> (<span>document</span>.fullscreenElement) {
<span>console</span>.log(<span>'hello fullscreen.'</span>);
}
<span>// cross-browser (except Safari Iphone)</span>
<span>if</span> (getPrefixedChild(<span>document</span>, <span>'fullscreenElement'</span>)) {
<span>console</span>.log(<span>'hello fullscreen.'</span>);
}
</code></pre>
</figure><div>
<h2><a href="https://snorpey.codes/de#events">Events</a></h2>
<p>Beim Öffnen oder Schließen des Vollbildmodus, wird jeweils das <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenchange_event"><code>fullscreenchange</code>-Event</a> ausgelöst. In <em>Safari</em> ist der Event-Name mit einem Prefix versehen und heißt <code>webkitfullscreenchange</code>.</p></div><figure>
<pre><code><span>// default</span>
<span>document</span>.exitFullscreen();
<span>// exitPromise returns a Promise</span>
<span>document</span>.exitFullscreen().then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'full screen was closed'</span>);
});
<span>// cross-browser method</span>
<span>const</span> exitFullScreen = getPrefixedElementMethod(<span>document</span>, <span>'exitFullScreen'</span>);
exitFullScreen();
exitFullScreen().then(<span><span>()</span> =></span> {
<span>console</span>.log(<span>'closed fullscreen'</span>);
});
</code></pre>
</figure><div>
<h1><a href="https://snorpey.codes/de#beispiel">Beispiel</a></h1>
<p>Im folgenden Beispiel habe ich alle Code-Snippets eingebaut</p></div><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>Vollbild Beispiel</a>
</div>
<figcaption>
<ul>
<li>
<a href="https://snorpey.codes/public/artikel/browser-fullscreen-apis/embeds/fullscreen-example.html">fullscreen-example</a>
</li>
</ul>
</figcaption>
</figure>
</div>Vue-Komponenten programmatisch mountenhttps://snorpey.codes/de/artikel/vue-komponenten-programmatisch-mountenGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2021-08-16T12:15:00+02:00In diesem Artikel zeige ich anhand einiger Code-Beispiele, wie man Vue.js Komponenten programmatisch mounten kann.<blockquote>
<p>In diesem Eintrag zeige ich, wie man Vue Komponenten <strong>programmatisch mounten</strong> kann, und wie man damit Vue-Komponenten und extern verwaltete DOM-Elemente ineinander <strong>verschachtelt</strong>.</p></blockquote><div>
<p>Ich setze das <a href="https://vuejs.org/">Vue.js</a> Frontend-Framework in vielen meiner Projekte ein. Es ist einfach zu lernen, gut dokumentiert, und die "Single-File-Component"-Architektur (<code>.vue</code>-Dateien) erleichtert es, wiederverwendbare Komponenten zu erstellen und zu warten.</p>
<p>Innerhalb einer Vue-Komponente werden alle DOM-Elemente von Vue verwaltet: Je nach Bedarf werden neue Elemente in dem DOM-Baum eingehängt oder entfernt.</p>
<p>Ein einfaches Beispiel:</p></div><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>Vue-Komponenten programmatisch mounten demo</a>
</div>
<figcaption>
<ul>
<li>
<a href="https://snorpey.codes/public/artikel/vue-komponenten-programmatisch-mounten/snippets/simple.vue">simple</a>
</li>
</ul>
</figcaption>
</figure>
</div><div>
<p>Hier wird bei Bedarf ein neues <code>h1</code>-Element erstellt oder wieder entfernt, sobald der Knopf gedrückt wird.</p>
<p>Ab und an ist es notwendig, ein Element von externem Code (z.B. einer Bibliothek) verwalten zu lassen, der nicht für die Benutzung mit Vue ausgelegt ist, so dass sich folgende Gliederung ergibt:</p></div><figure>
<pre><code> <span><<span>template</span>></span>
<span><<span>my-component</span>></span>
<span><<span>div</span> <span>class</span>=<span>"dom-element-from-external-library"</span>></span>
<span><<span>h1</span>></span>Hello from external library<span></<span>h1</span>></span>
<span></<span>div</span>></span>
<span></<span>my-component</span>></span>
<span></<span>template</span>></span></code></pre>
</figure><div>
<p>In Vue stehen unter Anderem die Lifecycle-Hooks (<code>mounted()</code>, <code>updated()</code>, <code>beforeDestroy()</code>) sowie die <code>$el</code> und <code>$ref</code> -Properties zur Verfügung, um externen Code an den Lebenszyklus einer Vue-Komponente anzuschließen.</p>
<p>Ein Beispiel:</p></div><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>Vue-Komponenten programmatisch mounten demo</a>
</div>
<figcaption>
<ul>
<li>
<a href="https://snorpey.codes/public/artikel/vue-komponenten-programmatisch-mounten/embeds/example/01.vue">01.vue</a>
</li>
<li>
<a href="https://snorpey.codes/public/artikel/vue-komponenten-programmatisch-mounten/embeds/example/third-party-library.js">third-party-library.js</a>
</li>
</ul>
</figcaption>
</figure>
</div><div>
<p>Manchmal kann es erforderlich sein, in einer Vue-Komponente per externer Bibliothek ein Kind-Element zu erzeugen, das wiederum eine weitere Vue-Komponente als Kind-Element haben soll. So dass ich folgende Gliederung ergibt:</p></div><figure>
<pre><code> <span><<span>template</span>></span>
<span><<span>my-component</span>></span>
<span><<span>div</span> <span>class</span>=<span>"dom-element-from-external-library"</span>></span>
<span><<span>h1</span>></span>Hello from external library<span></<span>h1</span>></span>
<span><<span>my-other-component</span> /></span>
<span></<span>div</span>></span>
<span></<span>my-component</span>></span>
<span></<span>template</span>></span></code></pre>
</figure><div>
<p>Die Implentation dieser Struktur ist etwas komplexer. Mit einer einfachen Helper-Funktion ist es möglich, Vue-Komponenten programmatisch in einem beliebigen Eltern-Element zu mounten.</p></div><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>Vue-Komponenten programmatisch mounten demo</a>
</div>
<figcaption>
<ul>
<li>
<a href="https://snorpey.codes/public/artikel/vue-komponenten-programmatisch-mounten/snippets/schema-2.vue">schema-2.vue</a>
</li>
<li>
<a href="https://snorpey.codes/public/artikel/vue-komponenten-programmatisch-mounten/embeds/example/mount-component.js">mount-component.js</a>
</li>
<li>
<a href="https://snorpey.codes/public/artikel/vue-komponenten-programmatisch-mounten/embeds/example/my-other-component.js">my-other-component.js</a>
</li>
<li>
<a href="https://snorpey.codes/public/artikel/vue-komponenten-programmatisch-mounten/embeds/example/third-party-library.js">third-party-library.js</a>
</li>
</ul>
</figcaption>
</figure>
</div><div>
<p>Die Helperfunktion habe ich hierbei von <a href="https://github.com/pearofducks/mount-vue-component/blob/master/index.js">pearofducks</a> github übernommen und leicht angepasst.</p></div>OpenStreetMap Karten selbst hostenhttps://snorpey.codes/de/artikel/openstreetmap-karten-selbst-hostenGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2021-07-13T16:10:00+02:00In diesem Artikel zeige ich, wie man OpenStreetmap-Karten auf dem eigenen Server komplett selbst hosten kann, komplett ohne Drittanbieter. Ich zeige dabei mei vorgehen und den Code, den ich dazu verwende.<div>
<p>Im diesem Artikel will ich zeigen, wie man interaktive OpenStreetmaps-Karten einfach komplett selbst hosten kann, ohne Code von Drittanbietern (z.B. MapBox, GoogleMaps) einzubinden, oder Requsests an sie zu senden.</p></div><div>
<figure>
<div>
<a>OpenStreetMap Karten selbst hosten demo</a>
</div>
<figcaption>
<p>Selbstgehostete OpenStreetMap Karte</p> </figcaption>
</figure>
</div><div>
<p>Bitte beachten: Diese Beschreibung beschränkt sich auf die Erstellung eines kleinen Kartenausschnitts in vielen Zoomstufen, um Datenmengen und Rechenzeiten gering zu halten. Die Erstellung einer Welt- oder Europakarte funktioniert technisch genauso, benötigt allerdings ein vielfaches von Rechenzeit und Speicherplatz.</p>
<p>Weiterhin beschreibt dieser Artikel auf das Erstellen der Vector Tiles auf MacOS, sowie das Hosting via Apache + PHP.</p></div><div>
<h2><a href="https://snorpey.codes/de#funktionsumfang">Funktionsumfang</a></h2>
<p>Welchen <strong>Funktionsumfang</strong> haben die hier beschriebenen selbst erstellten und gehosteten Karten?</p>
<ul>
<li>Interaktive Kartenansicht mit Zoom und Pan</li>
<li>Kleine Kartenausschnitte: Eine detaillierte Karte (mit vielen Zoomstufen) von Europa oder der Welt ist (je nach Budget) wahrscheinlich viel zu groß zum selberhosten</li>
<li>Kartenoverlays und Pins setzen</li>
<li>Generell der Funktionsumfang von <a href="https://maplibre.org/maplibre-gl-js-docs/example/">MaplibreGL</a></li>
<li>Hosting ist enweder per PHP + Apache (hier beschrieben) oder per Node.js möglich</li>
</ul>
<p>Was können diese selbst gehosteten Karten <strong>nicht</strong>?</p>
<ul>
<li>Navigation und Routing</li>
<li>Geocoding: also eine Adresse in Geokoordinaten konvertieren oder Koordinaten in Adresse umwandeln</li>
<li>weitere Komplexe Berechnungen, die auf einen Backend-Server angewiesen sind</li>
<li>Generell alles, was nicht dem Funktionsumfang von <a href="https://maplibre.org/maplibre-gl-js-docs/example/">MaplibreGL</a> entspricht :-)</li>
</ul></div><div>
<h2><a href="https://snorpey.codes/de#notwendige-schritte">Notwendige Schritte</a></h2>
<p>Zum Erstellen und Anzeigen der Karte sind zwei Schritte notwendig:</p>
<ol>
<li>Kartendaten im Vector-Tile Dateiformat werden aus Openstreetmap-Daten erzeugt</li>
<li>Die Vector-Tile-Dateien werden gehostet und via Web-Anwendung angezeigt</li>
</ol></div><div>
<figure>
<div>
<picture>
<img src="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/f0411d729c-1681853474/steps.svg" alt="Ein Diagramm, dass das die Schritte für die Erstellung und das selbsthosten von OpenStreetMap Karten zeigt" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
</figure>
</div><div>
<h2><a href="https://snorpey.codes/de#schritt-1-erstellung-von-vector-tile-dateien">Schritt 1: Erstellung von Vector-Tile Dateien</a></h2>
<p><a href="https://en.wikipedia.org/wiki/Vector_tiles">Vector Tiles</a> ermöglichen eine skalierbare Kartendarstellung: Die Karten "verpixeln" nicht beim zoomen.</p>
<p>Die Vector-Tiles-Dateien werden aus <a href="https://de.wikipedia.org/wiki/OpenStreetMap">Open Steet Map</a>-Karteninformationen generiert. Dazu kann man folgende Kommandozeilenwerkzeuge verwenden:</p>
<ul>
<li><a href="https://de.wikipedia.org/wiki/Make">make</a></li>
<li><a href="https://de.wikipedia.org/wiki/Bash_(Shell)">bash</a></li>
<li><a href="https://git-scm.com">git</a></li>
<li><a href="https://nodejs.org">nodejs</a></li>
<li><a href="https://docs.docker.com/engine/install/">docker</a></li>
<li><a href="https://docs.docker.com/compose/install/">docker-compose</a>: wird unter MacOS zusammen mit Docker installiert</li>
<li>optional: <a href="https://osmcode.org/osmium-tool/">osmium</a>: unter MaxOS am einfachsten per Homebrew installieren: <code>brew install osmium-tool</code></li>
</ul></div><div>
<h3><a href="https://snorpey.codes/de#karteninformationen-herunterladen">Karteninformationen herunterladen</a></h3>
<p>Die OpenStreetMap-Karteninformationen können von der OpenStreetmap-Webseite heruntergeladen werden: unter dem Menüpunkt <a href="https://www.openstreetmap.org/export">Export</a>:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-80x.webp 80w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-120x.webp 120w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-240x.webp 240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-320x.webp 320w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-480x.webp 480w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-600x.webp 600w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-720x.webp 720w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-800x.webp 800w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-880x.webp 880w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-920x.webp 920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-80x.avif 80w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-120x.avif 120w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-240x.avif 240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-320x.avif 320w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-480x.avif 480w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-600x.avif 600w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-720x.avif 720w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-800x.avif 800w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-880x.avif 880w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-920x.avif 920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/c5b7417091-1681853487/osm-data-export.png" alt="Export von Daten auf der OpenStreetMap Webseite" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
<figcaption>
<p>Kartendaten im osm-Format können auf der OpenStreetmap Webseite heruntergeladen werden.</p> </figcaption>
</figure>
</div><div>
<p>Die Kommandozeilenwerkzeuge in den den nächsten Schritten können mit <code>.osm</code>-Dateien nichts anfangen, sie benötigen die Kartendaten im <code>.osm.pbf</code>-Dateiformat.</p>
<p>Das <code>osmium</code> Kommandozeilenwerkzeug erledigt diese Aufgabe. Der folgende Befehl wandelt die <code>.osm</code>-Datei in eine <code>.osm.pbf</code>-Datei um:</p></div><figure>
<pre><code>$ osmium cat my_test_map.osm -o my_test_map.osm.pbf</code></pre>
</figure><div>
<p>Wer seine Karten direkt im <code>.osm.pbf</code>-Format herunterlädt, zum Beispiel von <a href="https://download.geofabrik.de/">Geofabrik.de</a>, kann sich die Installation von <code>osmium</code> und das Umwandeln sparen.</p></div><div>
<h3><a href="https://snorpey.codes/de#vector-tiles-erzeugen">Vector-Tiles erzeugen</a></h3>
<p>Als Nächstes werden aus den <code>.osm.pbf</code>-Dateien Vector-Tiles im <code>.mbtiles</code> Format erstellt, die im Browser angezeigt werden können. Hierbei kommt das <a href="https://github.com/openmaptiles/openmaptiles">Open Map Tiles</a> Projekt zum Einsatz, das per Git heruntergeladen und installiert wird:</p></div><figure>
<pre><code>$ git <span>clone</span> git@github.com:openmaptiles/openmaptiles.git
$ <span>cd</span> openmaptiles</code></pre>
</figure><div>
<p>Für <em>openmaptiles</em> kann man jetzt noch einige Einstellungen anpassen, bevor die Vector Tiles erzeugt werden: In der <code>.env</code>-Datei im <code>opemaptiles/</code> Ordner kann man dazu zum Beispiel den minimalen und maximalen Zoomlevel der Karte anpassen:</p></div><figure>
<pre><code>MIN_ZOOM=0
MAX_ZOOM=7</code></pre>
</figure><div>
<p>Je höher der Maximale Zoomlevel, desto detailreicher die Karte, und desto länger dauert der Render-Prozess. Für die oben eingebunde Karte habe ich beispielsweise Zoomlevel von <em>2</em>-<em>18</em> eingestellt.</p>
<p>Jetzt können die Vector-Tile-Dateien erzeugt werden. Dies geschieht mit dem Befehl:</p></div><figure>
<pre><code>$ ./quickstart.sh my_test_map</code></pre>
</figure><div>
<p>Dieser Prozess kann <strong>sehr lange</strong> dauern. Je nach eingestelltem Zoomlevel und Kartengebiet auch mehrere Stunden.</p>
<p>Die erzeugten Vector-Tiles-Dateien werden im Anschluss unter <code>openmaptiles/data/tiles.mbtiles</code> abgelegt.</p></div><div>
<h2><a href="https://snorpey.codes/de#schritt-2-karte-anzeigen">Schritt 2: Karte Anzeigen</a></h2>
<p>Die Karte wird mithilfe der <a href="https://github.com/maplibre/maplibre-gl-js">MapLibreGL</a>-Bibliothek in eine Seite eingebettet. Zusätzlich werden (je nach gewünschtem Kartenstil) noch einige weitere NodeJS-Tools eingesetzt.</p>
<h3><a href="https://snorpey.codes/de#styles-erstellen">Styles erstellen</a></h3>
<p>Nachdem im Browser Anzeigbare Karteninformationen nun in Form von Vector-Tiles-Dateien vorliegen, geht es als Nächstes darum, sie in eine gut lesbare Form zu bringen, mithilfe von Kartenstilen (oder Mapstyles):</p>
<p>Diese sind in einer Datei namens <code>styles.json</code> enthalten und beschreiben Aussehen Farbgebung und Anmutung verschiedener Kartenelemente.</p>
<p>Eine Spezifikation des Mapstyles-Formats ist auf der Webseite von <a href="https://maplibre.org/maplibre-gl-js-docs/style-spec/">maplibre</a> einsehbar.</p>
<p>Wer kein Interesse hat, eigene Karten-Styles zu schreiben, der kann auch die vorgefertigten Styles von OpenMapTiles übernehmen. In diesem Beispiel verwende ich die Styles von <a href="https://github.com/openmaptiles/osm-bright-gl-style">OSM Bright</a></p>
<p>Dafür lade ich die Styles per <code>git clone</code> herunter:</p></div><figure>
<pre><code>$ git <span>clone</span> git@github.com:openmaptiles/osm-bright-gl-style.git</code></pre>
</figure><div>
<h3><a href="https://snorpey.codes/de#sprites-erstellen">Sprites erstellen</a></h3>
<p>Für viele Kartenstyles werden auch Icons und Symbole benötigt, um zum Beispiel Autobahnen richtig zu kennzeichnen. Diese Symbole sind im Bright-OSM Theme im <code>icons</code>-Ordner im svg-Format hinterlegt.</p>
<p>Das Command-Line Tool <a href="https://github.com/elastic/spritezero-cli">spritezero-cli</a> kann die einzelnen SVG-Dateien zu einem Spritesheet zusammenfügen, um Ladezeiten zu optimieren.</p>
<p>Dieses kann man global via npm installieren:</p></div><figure>
<pre><code>$ npm install -g @elastic/spritezero-cli</code></pre>
</figure><div>
<p>Per</p></div><figure>
<pre><code>$ spritezero sprite@2x icons --retina && spritezero sprite icons</code></pre>
</figure><div>
<p>wird aus dem "icons" Ordner ein neues Spritesheet mit dem namen "sprite" erstellt: Es besteht aus den Dateien <code>sprite.png</code> und <code>sprite.json</code></p></div><figure>
<pre><code>.
├── icons
│ ├── ... (svg icons)
│ └── ...
├── sprite.png
└── sprite.json</code></pre>
</figure><div>
<h3><a href="https://snorpey.codes/de#schritt-4-schriftdateien-erzeugen">Schritt 4: Schriftdateien erzeugen</a></h3>
<p>Neben den Vector-Tiles und Style-Dateien werden für die richtige Anzeige der Karten in der Regel auch Schriften benötigt. Die Schriftdateien werden im <code>.pbf</code>-Dateiformat benötigt. Bei der Umwandlung von Schriftdateien in anderen Formaten (z.B. <code>.ttf</code>) hilft das Paket <a href="https://github.com/openmaptiles/fonts">openmaptiles/fonts</a>, das netterweise auch schon einige Standartschriftarten mitbringt:</p>
<p>Dazu das Paket per <code>git clone</code> herunterladen und dann per <code>npm install</code> die Abhängigkeiten installieren:</p></div><figure>
<pre><code>$ git <span>clone</span> git@github.com:openmaptiles/fonts.git
$ <span>cd</span> fonts
$ npm install</code></pre>
</figure><div>
<p>Per <code>node ./generate.js</code> werden die <code>pbf</code>-Dateien im <code>_output</code> Ordner erzeugt.</p></div><div>
<h3><a href="https://snorpey.codes/de#tileserver-einrichten">Tileserver einrichten</a></h3>
<p>Nachdem alle Vorarbeiten abgeschlossen sind, geht es schließelich darum, eine Interaktive Karte im Browser anzuzeigen: Dabei kommt serverseitig ein Tileserver zum Einsatz: Der Tileserver gibt auf HTTP-Anfragen den angefragtem Kartenausschnitt zurück. Dabei das Projekt <a href="https://github.com/maptiler/tileserver-php">https://github.com/maptiler/tileserver-php</a> verwendet.</p>
<p>Für die Installation des Tileservers sind folgende Dateien aus dem Projekt herunterzuladen und in einen öffentlich erreichbaren Ordner eines Webservers zu kopieren (also z.B. in den <code>htdocs</code>-Ordnervon Apache):</p>
<ul>
<li><code>.htaccess</code></li>
<li><code>tileserver.php</code></li>
</ul>
<p>Als nächstes noch die erzeugte Datei mit den Vector-Tiles <code>tiles.mbtiles</code> in den gleichen Ordner kopieren.</p>
<p>Um zu testen ob alles funktioniert hat kann man als nächstes die Adresse von <code>tileserver.php</code> im Browser aufrufen, also z.B: <code>http://localhost/tileserver.php</code>.</p>
<p>Wenn alles geklappt hat, sollte das Tileserver-Interface erscheinen:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-80x.webp 80w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-120x.webp 120w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-240x.webp 240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-320x.webp 320w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-480x.webp 480w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-600x.webp 600w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-720x.webp 720w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-800x.webp 800w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-880x.webp 880w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-920x.webp 920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-80x.avif 80w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-120x.avif 120w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-240x.avif 240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-320x.avif 320w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-480x.avif 480w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-600x.avif 600w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-720x.avif 720w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-800x.avif 800w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-880x.avif 880w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-920x.avif 920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/22b9f6969b-1681853474/tileserver-interface.png" alt="die tileserver-benutzerobefläche im browser" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
<figcaption>
<p>Das Tileserver.php Interface</p> </figcaption>
</figure>
</div><div>
<p>Um eine Vorschau der erstellten Vector Tiles zu sehen, kann man im Menü auf der rechten Seite unter <code>Open Layers 3</code> auf <code>Preview</code> klicken und dann links unten den Haken bei "Show OSM" setzen. Das erleichtert das Heranzoomen in der Karte auf den gerenderten Bereich:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-80x.webp 80w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-120x.webp 120w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-240x.webp 240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-320x.webp 320w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-480x.webp 480w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-600x.webp 600w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-720x.webp 720w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-800x.webp 800w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-880x.webp 880w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-920x.webp 920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-80x.avif 80w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-120x.avif 120w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-240x.avif 240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-320x.avif 320w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-480x.avif 480w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-600x.avif 600w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-720x.avif 720w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-800x.avif 800w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-880x.avif 880w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-920x.avif 920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/openstreetmap-karten-selbst-hosten/e37f0d6cf8-1681853481/tileserver-preview.png" alt="Die Tileserver-Oberfläche mit einem befüllten Kartenausschnitt" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
<figcaption>
<p>Tileserver.php Interface mit Kartendaten</p> </figcaption>
</figure>
</div><div>
<p>Als nächstes geht es daran, die Kartenstile, Icons und die erzeugten Schriftdateien ebenfalls auf den Webserver hochzuladen. In welchem Ordner die Dateien abgelegt werden ist nicht relevant, wichtig ist nur, dass die hochgeladenen Dateien über eine URL abrufbar sind.</p>
<p>Sind Kartenstile, Icons und Schriftdateien hochgeladen, müssen diese mit in der Kartenstil-Datei <code>styles.json</code> richtig verlinkt werden, und zwar jeweils mit der richtigen URL.</p>
<p>Dazu müssen in der JSON-Datei folgende Einträge angepasst werden:</p>
<ul>
<li>Unter <code>sources.openmaptiles.url</code> wird die URL zu den Karteninformationen auf dem Tileserver eingetragen, also z.B: <code>"http://localhost/tileserver/wuerzburg2.json"</code>.</li>
<li>Unter <code>glyphs</code> wird die URL zu den Schriftdateien eingetragen, also z.B: <code>http://localhost/fonts/{fontstack}/{range}.pbf</code>. Die Werte <code>{fontstack}</code> und <code>{range}</code> werden später programmatisch ersetzt.</li>
<li>Unter <code>sprite</code>: wird die Ordner-URL der Icons eingetragen, also z.B: <code>http://localhost/icons</code></li>
</ul></div><div>
<p>Insgesamt sollte die Ordnerstruktur auf dem Server jetzt in etwa so aussehen:</p></div><figure>
<pre><code>.
├── fonts
│ ├── ...
│ └── ...
├── mapstyle.php
├── styles
│ ├── sprite.json
│ ├── sprite.png
│ ├── sprite@2x.json
│ ├── sprite@2x.png
│ └── style.json
└── tiles
├── tileserver.php
└── tiles.mbtiles</code></pre>
</figure><div>
<h3><a href="https://snorpey.codes/de#einbettung-per-maplibregl">Einbettung per MapLibreGL</a></h3>
<p>Als letzter Schritt bleibt schließlich nur noch, die Interaktive Karte anzuzeigen. Dabei verwende ich die <a href="https://github.com/maplibre/maplibre-gl-js">maplibre-gl-js</a> Javascript Bibliothek:</p></div><figure>
<pre><code><span><!doctype <span>html</span>></span>
<span><<span>html</span>></span>
<span><<span>head</span>></span>
<span><<span>meta</span> <span>charset</span>=<span>"utf-8"</span> /></span>
<span><<span>title</span>></span>Self hosted OSM-Widget<span></<span>title</span>></span>
<span><<span>style</span>></span><span>
<span>#map</span> { <span>width</span>: <span>100%</span>; <span>height</span>: <span>90vh</span>; }
</span><span></<span>style</span>></span>
<span></<span>head</span>></span>
<span><<span>body</span>></span>
<span><<span>div</span> <span>id</span>=<span>"map"</span>></span><span></<span>div</span>></span>
<span><<span>script</span> <span>src</span>=<span>"maplibre-gl.js"</span>></span><span></<span>script</span>></span>
<span><<span>script</span>></span><span>
<span>const</span> map = <span>new</span> maplibregl.Map( {
container: <span>'#map'</span>,
style: <span>'assets/styles.json'</span>,
center: <span>new</span> maplibregl.LngLat( <span>9.928392619016336</span>, <span>49.79308196069323</span> ),
zoom: <span>16</span>
} );
<span>const</span> marker = <span>new</span> maplibregl.Marker( { color: <span>'#000000'</span> } )
.setLngLat( [ <span>9.928392619016336</span>, <span>49.79308196069323</span> ] )
.addTo( map );
</span><span></<span>script</span>></span>
<span></<span>body</span>></span>
<span></<span>html</span>></span></code></pre>
</figure><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>Einfaches Kartenwidget mit MapLibreGL</a>
</div>
<figcaption>
<p>Eine einfache Karte mit Pin</p> <ul>
<li>
<a href="https://snorpey.codes/public/artikel/openstreetmap-karten-selbst-hosten/embeds/map/map-3d.html">map-3d.html</a>
</li>
<li>
<a href="https://snorpey.codes/public/artikel/openstreetmap-karten-selbst-hosten/embeds/map/backend/mapstyle.php">mapstyle.php</a>
</li>
</ul>
</figcaption>
</figure>
</div>Three.js: Animationsfortschritt programmatisch steuernhttps://snorpey.codes/de/artikel/three-js-animationsfortschritt-programmatisch-steuernGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2021-05-16T21:50:00+02:00In diesem Artikel beschreibe ich anhand von einfachen Code-Beispielen, wie man in Three.js den GLTF-Animationsfortschritt programmatisch steuern kann.<div>
<figure>
<div>
<a>Three.js: Animationsfortschritt programmatisch steuern demo</a>
</div>
<figcaption>
<p>Beispiel einer per Slider gesteuerten Three.js-Animation</p> </figcaption>
</figure>
</div><div>
<p>Kürzlich habe ich an einem Projekt gearbeitet, in dem wir die WebGL-Bibliothek <a href="https://threejs.org">Three.js</a> einsetzten, um 3D-Animationen abzuspielen. Unter Anderem ging es darum, eine 3D-Animation in Three.js zu importieren, und diese dann per dem Scrollfortschritt abzuspielen.</p>
<p>Für eine unkomplizierte Zusammmenarbeit zwischen Entwickler und Motion-Designer, entschieden wir uns im Team, das <a href="https://www.khronos.org/gltf/">GLTF</a>-Dateiformat einzusetzen. Dieses ermöglicht es, komplette 3D-Szenen inclusive Beleuchtungs- und Animationsdaten aus einem 3D-Model Programm wie Blender oder Cinema 4D zu exportieren und einfach in <em>Three.js</em> zu importieren.</p>
<p>Das importieren einer 3D-Szene funktionierte einwandfrei:</p></div><div>
<figure>
<div>
<a>Three.js: Animationsfortschritt programmatisch steuern demo</a>
</div>
<figcaption>
<p>Aus Blender exportiert eine Beispielszene im GLTF-Format</p> </figcaption>
</figure>
</div><div>
<p>Auch das Abspielen von 3D-Animationen funktionierte problemlos: (Die Beispielanimationen konnte ich übrigens sehr schnell und einfach mithilfe des <a href="https://www.youtube.com/watch?v=a7qyW1G350g">großartigen Tutorials</a> von Polyfjord erstellen)</p></div><div>
<figure>
<div>
<a>Three.js: Animationsfortschritt programmatisch steuern demo</a>
</div>
<figcaption>
<p>In die GLTF-Datei eingebettete Animationen können Problemlos abgespielt werden</p> </figcaption>
</figure>
</div><div>
<p>Sobald ich aber versuchte, den Animationsfortschritt programmatisch festzulegen um ihn durch Scrollfortschritt zu steuern, stand ich allerdings vor einem größeren Problem als gedacht:</p>
<p>Three js ermöglicht es zwar, eine Animation (oder Clip) per Methode abzuspielen. Aber eine einfache Möglichkeit, denn Animationsfortschritt eines Clips per Methode festzulegen, (wie z.B: <code>clip.setProgress(0.3)</code>), sucht man vergebens.</p>
<p>Nach langer Recherche fand ich eine Lösung: Unter der Haube setzt Three.js sogenannte <a href="https://threejs.org/docs/#api/en/math/Interpolant">Interpolanten</a> ein. Diese können für jedes animierte Property eines Objektes die Werte zu für einen angegebenen Zeitpunkt zurückgeben.</p>
<p>Im Folgenden der Beispiel-Code für die oben gezeigte Demo:</p></div><div data-show-source="Quelltext anzeigen">
<figure>
<div>
<a>Three.js: Animationsfortschritt programmatisch steuern demo</a>
</div>
<figcaption>
<p>Per Nutzereingabe steuerbare GLTF-Animation in Three.js</p> <ul>
<li>
<a href="https://snorpey.codes/public/artikel/three-js-animationsfortschritt-programmatisch-steuern/example-2/embeds/03.html">03</a>
</li>
</ul>
</figcaption>
</figure>
</div>In Kirby externe Links automatisch in neuem Tab öffnenhttps://snorpey.codes/de/artikel/in-kirby-externe-links-automatisch-in-neuem-tab-oeffnenGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2020-02-19T19:30:00+01:00In diesem Artikel zeige ich anhand von Code-Beispielen wie man im Kirby CMS Links, die auf externe URLs zeigen automatisch in neuen Tabs öffnen lassen kann<div>
<p>Es ist ja weitgehend bekannt, dass man per <code>target="_blank"</code> Links in einem neuen Fenster öffnen kann.</p>
<p>Für externe Links macht es oft Sinn, zusätzlich noch <code>rel="noopener noreferrer"</code> hinzuzufügen, um den Inhalt der aktuellen Seite abzusichern. Weitere Informationen und eine Demo zu dem Thema gibt es in dem <a href="https://mathiasbynens.github.io/rel-noopener/">Blog-Eintrag</a> von Mathias Bynens zu diesem Thema.</p>
<p>Diese Funktionalität kann man zwar einfach in Kirby's WYSIWYG-Markdown Editor über den <code>(link)</code>-Tag hinzufügen, es besteht aber gerade bei längeren Texten mit vielen Links die Möglichkeit, dass man das einmal vergisst.</p>
<p>Um dem vorzubeugen, habe ich das Hinzufügen von <code>target="_blank"</code> und <code>rel="noopener noreferrer"</code> automatisiert: Die entsprechenden Attribute werden beim Rendern einer Seite von Kirby selbst hinzugefügt.</p>
<p>Für diese Funktionalität benutze ich Kirby Hooks. Dazu lege ich in der <code>confg.php</code> unter <code>hooks</code> zunächst einen neuen Eintrag namens <code>'kirbytext:after'</code> an:</p></div><figure>
<pre><code><span><?php</span>
<span>return</span> [
<span>'hooks'</span>: [
<span>'kirbytext:after'</span> => <span><span>function</span> <span>( $text )</span> </span>{
<span>return</span> $text;
}
]
];</code></pre>
</figure><div>
<p>Diese Funktion wird für jedes Feld mit dem Typ <code>kirbytext</code> ausgeführt. Als Argument wird der aus dem Markdown generierte HTML-String übergeben. Es wird erwartet, dass diese Funktion den HTML-String verändert, und ihn dann am Ende auch wieder zurückgibt.</p>
<p>Um alle Anker-Elemente in diesem HTML-String zu finden und modifizieren könnte man einen komplizierten <a href="https://de.wikipedia.org/wiki/Regul%C3%A4rer_Ausdruck">regulären Ausdruck</a> verwenden.</p>
<p>Deutlich einfacher funktioniert es aber mit der in PHP mitgelieferten <a href="https://www.php.net/manual/en/book.dom.php">DOM-Bibliothek</a>, mit der sich DOM-Knoten leicht erstellen und bearbeiten lassen:</p></div><figure>
<pre><code><span><?php</span>
<span>return</span> [
<span>'hooks'</span> => [
<span>'kirbytext:after'</span> => <span><span>function</span> <span>( $text )</span> </span>{
<span>if</span> ( strlen( $text ) > <span>0</span> ) {
<span>// Get current page host</span>
<span>// (he attributes will only be set for external links)</span>
$site_host = parse_url( site()->url() )[<span>'host'</span>];
<span>// Convert $text to DOM tree</span>
$dom = <span>new</span> DomDocument();
$dom->loadHTML( $text );
<span>// Loop over all anchor elements</span>
<span>foreach</span> ( $dom->getElementsByTagName( <span>'a'</span> ) <span>as</span> $link_el ) {
<span>// Parse link address</span>
$link_href = $link_el->getAttribute( <span>'href'</span> );
$link_parts = parse_url( $link_href );
<span>if</span> (
$link_parts &&
<span>isset</span>( $link_parts[<span>'host'</span>] ) &&
<span>isset</span>( $link_parts[<span>'scheme'</span>] )
) {
$link_host = $link_parts[<span>'host'</span>];
$link_scheme = $link_parts[<span>'scheme'</span>];
<span>// Only continue if the link is external</span>
<span>if</span> (
in_array( $link_scheme, [ <span>'http'</span>, <span>'https'</span> ] ) &&
$link_host !== $site_host
) {
<span>// Create string of old link (to find and replace it later)</span>
$link_str = $dom->saveHTML( $link_el );
<span>// Add link attributes</span>
$link_el->setAttribute( <span>'rel'</span>, <span>'noopener noreferrer'</span> );
$link_el->setAttribute( <span>'target'</span>, <span>'_blank'</span> );
<span>// Create new link string</span>
$new_link_str = $dom->saveHTML( $link_el );
<span>// replace old link with new link in $text</span>
$text = str_replace( $link_str, $new_link_str, $text );
}
}
}
}
<span>return</span> $text;
}
]
];</code></pre>
</figure><div>
<h1><a href="https://snorpey.codes/de#automatische-lesemarken">Automatische Lesemarken</a></h1>
<p>Auf ähnliche Weise kann man zu allen Überschriften einen Link mit automatisch generierter ID hinzufügen, um einfach zwischen Abschnitten springen zu können:</p></div><figure>
<pre><code><span><?php</span>
<span>return</span> [
<span>'hooks'</span> => [
<span>'kirbytext:after'</span> => <span><span>function</span> <span>( $text )</span> </span>{
<span>if</span> ( strlen( $text ) > <span>0</span> ) {
$dom = <span>new</span> DomDocument();
$dom->loadHTML( $text );
<span>// Tags to be linked</span>
$tags_with_id = [ <span>'h1'</span>, <span>'h2'</span>, <span>'h3'</span> ];
<span>foreach</span> ( $tags_with_id <span>as</span> $tag_name ) {
<span>foreach</span> ( $dom->getElementsByTagName( $tag_name ) <span>as</span> $item ) {
$item_content = $item->nodeValue;
<span>if</span> ( $item_content ) {
<span>// Create string for old element (to find and replace later)</span>
$item_str = $dom->saveHTML( $item );
<span>// Generate element ID from element text content</span>
$id = Str::slug( $item_content . <span>''</span> );
<span>// Set element ID</span>
$item->setAttribute( <span>'id'</span>, $id );
<span>// Create a new anchor element and</span>
<span>// move all element childNodes to the new anchor.</span>
<span>// Attach insert anchor into element</span>
$link_el = $dom->createElement( <span>'a'</span> );
$link_el->setAttribute( <span>'href'</span>, <span>'#'</span> . $id );
$link_el->appendChild( $item->firstChild );
$item->insertBefore( $link_el, $item->firstChild );
<span>// Generate new HTML string of element</span>
$new_item_str = $dom->saveHTML( $item );
<span>// In replace old element with new element in $text</span>
$text = str_replace( $item_str, $new_item_str, $text );
}
}
}
<span>return</span> $text;
}
]
];</code></pre>
</figure>Kirby CMS Live-Reload-Serverhttps://snorpey.codes/de/artikel/kirby-cms-live-reload-serverGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2019-10-19T14:05:00+02:00Für einfache Webseiten setze ich gerne Kirby ein: das ist ein flat-file CMS, das in PHP geschrieben ist.Bei einem Flat-File Content Management System werden die Textinhalte ausschließlich in Dateien gespeichert. Mehr dazu auf Wikipedia.<div>
<p>Für einfache Webseiten setze ich gerne <a href="https://getkirby.com/">Kirby</a> ein: das ist ein flat-file CMS, das in PHP geschrieben ist.</p></div><div>
<details>
<summary>Was ist ein Flat-File Content Management System?</summary>
<div>
<p>Bei einem Flat-File Content Management System werden die Textinhalte ausschließlich in Dateien gespeichert. Mehr dazu auf <a href="https://de.wikipedia.org/wiki/Flat-File-Content-Management-System">Wikipedia</a>.</p> </div>
</details>
</div><div>
<p>Um für die Entwicklung einen lokalen PHP-Server zu starten, setzt man in der Regel Programme wie <a href="https://www.apachefriends.org/download.html">XAMPP</a> oder <a href="https://www.mamp.info/en/">MAMP</a> ein, da diese neben einem lokalen Webserver auch eine Datenbank (meist MySQL) mitbringen.</p>
<p>Da Kirby keine Datenbankanbindung benötigt (als Flat-File CMS benötigt es keine Datenbank), kann man es auch über den in <a href="https://www.php.net/manual/en/features.commandline.webserver.php">PHP eingebauten Server</a> laufen lassen, und ist so nicht auf XAMPP oder MAMP angewiesen:</p></div><figure>
<pre><code>$ php -S localhost:8000 kirby/router.php</code></pre>
</figure><div>
<p>Das hat bei mir bis jetzt immer einwandfrei funktioniert (dazu bitte die <a href="https://getkirby.com/docs/cookbook/setup/development-environment#php-s-built-in-server">Hinweise</a> auf der Kirby-Webseite beachten), allerdings hätte ich zum komfortablen Entwickeln gerne noch eine Live-Reload-Funktionalität.</p>
<p>Glücklicherweise lässt sich diese über den JavaScript-Packagemanager <a href="https://nodejs.org/en/">NPM</a> relativ einfach nachrüsten, denn neben der Paketverwaltung bietet NPM noch eine weitere, praktische Funktion: Das einfache Ausführen von Scripts per <code>npm run</code>.</p>
<p>Dazu ist zunächst eine <code>package.json</code> Datei im Rootverzeichnis des Kirby-Projekts notwendig (falls noch nicht vorhanden kann sie per <code>npm init</code> erstellt werden).</p>
<p>Danach sollten per <code>npm install --save-dev browser-sync concurrently</code> die beiden Pakete <a href="https://www.npmjs.com/package/browser-sync">browser-sync</a> und <a href="https://www.npmjs.com/package/concurrently">concurrently</a> installiert werden.</p>
<p>Als nächstes eine Datei namens <code>browsersync.js</code>, im Rootverzeichnis des Projektes anlegen, mit folgendem Inhalt:</p></div><figure>
<pre><code><span>const</span> browserSync = <span>require</span>( <span>'browser-sync'</span> );
<span>const</span> syncOptions = <span>require</span>( <span>'./package.json'</span> ).browserSync;
<span>const</span> filesToWatch = syncOptions.watch || [ ];
<span>const</span> filesToIgnore = syncOptions.ignore || [ ];
<span>const</span> proxy = syncOptions.proxy;
<span>const</span> sync = browserSync.create();
sync.init( {
<span>watch</span>: <span>true</span>,
proxy,
<span>files</span>: filesToWatch,
<span>ignore</span>: filesToIgnore,
<span>open</span>: <span>'local'</span>
} );</code></pre>
</figure><div>
<p>In dieser Datei wird der Browsersync Server gestartet, mit den in der <code>package.json</code> definierten Optionen. Weitere Informationen zu den möglichen Optionen gibt es auf der <a href="https://www.browsersync.io/docs/options">Dokumentationsseite</a> von Browsersync.</p>
<p>Bevor man den Server starten kann, müssen in der <code>package.json</code> Datei noch ein paar Änderungen vorgenommen werden:</p></div><figure>
<pre><code>{
<span>"name"</span>: <span>"kirby-browsersync-demo"</span>,
<span>"version"</span>: <span>"1.0.0"</span>,
<span>"description"</span>: <span>"My Kirby Browsersync Demo"</span>,
<span>"scripts"</span>: {
<span>"serve"</span>: <span>"concurrently \"npm run serve-php\" \"npm run sync\""</span>,
<span>"serve-php"</span>: <span>"php -S localhost:8000 kirby/router.php &> /dev/null"</span>,
<span>"sync"</span>: <span>"node browsersync.js"</span>
},
<span>"devDependencies"</span>: {
<span>"browser-sync"</span>: <span>"^2.26.7"</span>,
<span>"concurrently"</span>: <span>"^5.0.0"</span>
},
<span>"browserSync"</span>: {
<span>"proxy"</span>: <span>"localhost:8000"</span>,
<span>"watch"</span>: [
<span>"assets"</span>,
<span>"content"</span>,
<span>"site/snippets"</span>,
<span>"site/templates"</span>,
<span>"site/blueprints"</span>,
<span>"site/languages"</span>
],
<span>"ignore"</span>: [
<span>"**/.lock"</span>,
<span>"**/.DS_Store"</span>
]
}
}</code></pre>
</figure><div>
<p>Die Einträge im einzelnen:</p>
<ul>
<li>Unter <code>scripts</code>:</li>
<li><code>serve-php</code> Startet den in PHP eingebauten Webserver und unterdrückt alle <code>200</code> Access logs (sind sehr viele)</li>
<li><code>sync</code> Startet den BrowserSync Server</li>
<li><code>serve</code> Startet <code>serve-php</code> und <code>sync</code> gleichzeitig</li>
<li>Unter <code>browserSync</code> werden die Optionen für den Browsersync Server definiert:</li>
<li><code>proxy</code>: Die Adresse des PHP Servers</li>
<li><code>watch</code>: Die Ordner, die BrowserSync beobachten soll. Falls sich darin eine Datei ändert, wird die Seite neu geladen</li>
<li><code>ignore</code>: Dateien, die keinen Reload auslösen sollen</li>
</ul>
<p>Falls alles funktioniert hat, kann man jetzt per</p>
<p>npm run serve</p>
<p>das Projekt starten. Es sollte sich automatisch ein neues Browserfenster mit dem Projekt öffnen.</p>
<p><strong>Wichtig dabei</strong>: BrowserSync funktioniert nur, wenn es in dem ausgelieferten HTML ein <code><body></code>-Element gibt (weitere Informationen dazu <a href="https://browsersync.io/docs#requirements">hier</a>).</p></div>Math.floor() und ~~https://snorpey.codes/de/artikel/math-floor-und-bitwise-operatorenGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2019-05-19T13:45:00+02:00Mit den Bitweise Operatoren in JavaScript kann man allerlei interessante Dinge anstellen.So ist es beispielsweise möglich, den Operator "Bitweise Negation" (die Tilde~) zu benutzen, um Zahlen abzurunden:<div>
<p>Mit den <a href="https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Bitwise_Operatoren">Bitweise Operatoren</a> in JavaScript kann man allerlei interessante Dinge anstellen.</p>
<p>So ist es beispielsweise möglich, den Operator "Bitweise Negation" (die Tilde<code>~</code>) zu benutzen, um Zahlen abzurunden:</p></div><div>
<p>Mit den <a href="https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Bitwise_Operatoren">Bitweise Operatoren</a> in JavaScript kann man allerlei interessante Dinge anstellen.</p>
<p>So ist es beispielsweise möglich, den Operator "Bitweise Negation" (die Tilde<code>~</code>) zu benutzen, um Zahlen abzurunden:</p></div><div>
<p>Hilfreich ist es deswegen, weil das Abrunden einer Zahl mittels des "Bitweise Negation"-Operators in vielen Browsern deutlich schneller ausgeführt wird, als die die Verwendung von <code>Math.floor</code>.</p>
<p>Diese Eigenschaft ist insbesondere dann nützlich, wenn das Abrunden einer Zahl sehr oft wiederholt werden muss, wie zum Beispiel beim manipulieren der Pixeldaten eines <code><canvas></code>-Elements, bei dem mehrere tausend Pixel einzeln verglichen werden müssen.</p>
<p>Da ich genau diesen Anwendungsfall in meinen Projekten (siehe z.B: <a href="https://github.com/snorpey/glitch-canvas">glitch-canvas</a> relativ oft habe, hatte ich mir angewohnt, allgemein <code>~~</code> als Ersatz von <code>Math.floor()</code> einzusetzen.</p>
<p>Das war ein Fehler.</p>
<p>Denn es gibt Zahlen, für welche die Ergebnisse von <code>~~</code> und <code>Math.floor()</code> <em>nicht</em> übereinstimmen:</p></div><div>
<p>Hilfreich ist es deswegen, weil das Abrunden einer Zahl mittels des "Bitweise Negation"-Operators in vielen Browsern deutlich schneller ausgeführt wird, als die die Verwendung von <code>Math.floor</code>.</p>
<p>Diese Eigenschaft ist insbesondere dann nützlich, wenn das Abrunden einer Zahl sehr oft wiederholt werden muss, wie zum Beispiel beim manipulieren der Pixeldaten eines <code><canvas></code>-Elements, bei dem mehrere tausend Pixel einzeln verglichen werden müssen.</p>
<p>Da ich genau diesen Anwendungsfall in meinen Projekten (siehe z.B: <a href="https://github.com/snorpey/glitch-canvas">glitch-canvas</a> relativ oft habe, hatte ich mir angewohnt, allgemein <code>~~</code> als Ersatz von <code>Math.floor()</code> einzusetzen.</p>
<p>Das war ein Fehler.</p>
<p>Denn es gibt Zahlen, für welche die Ergebnisse von <code>~~</code> und <code>Math.floor()</code> <em>nicht</em> übereinstimmen:</p></div><div>
<p>Viele Abkürzungen im Code sind für Spezialfälle hilfreich, sie sollte allerdings mit Bedacht eingesetzt und nicht verallgemeinert werden. Deswegen sind sie ja Abkürzungen.</p>
<p>Hamma wieder was gelernt, recht herzlichen Dank für die Aufmerksamkeit, bis zum nächsten Mal!</p></div>Nextcloud Kalender im Unterordner synchronisierenhttps://snorpey.codes/de/artikel/nextcloud-sync-in-unterordnerGeorg Fischerhi@snorpey.codeshttps://snorpey.codes2018-08-17T17:35:00+02:00Vor einigen Wochen habe ich auf meinem Webspace eine Nextcloud Instanz eingerichtet.Mein Vertrauen in die Integrität der Anbieter von großen, kostenlosen Cloud-Diensten schwindet zusehens, und so setzte ich mir zum Ziel, möglichst viele möglichst viele meiner Daten von diesen Diensten auf meinen eigenen Webspace umzuziehen.<div>
<p>Vor einigen Wochen habe ich auf meinem Webspace eine Nextcloud Instanz eingerichtet.</p>
<p>Mein Vertrauen in die Integrität der Anbieter von großen, kostenlosen Cloud-Diensten schwindet zusehens, und so setzte ich mir zum Ziel, möglichst viele möglichst viele meiner Daten von diesen Diensten auf meinen eigenen Webspace umzuziehen.</p></div><div>
<details>
<summary>Was ist Nextcloud?</summary>
<div>
<p><a href="https://nextcloud.com">Nextcloud</a> ist eine freie Software zur Speicherung von Daten auf einem eigenen Server. Dies ermöglicht die eine einfache Synchronisation der Daten auf verschiedenen Endgeräten.</p>
<p>Wie bei Dropbox stehen für Nextcloud kostenlose Programme für alle gängigen Betriebssysteme zur Verfügung, mit denen sich die automatische Synchronisation von Daten auf der eigenen Festplatte leicht einrichten lässt. </p></div>
</details>
</div><div>
<p>Nextcloud ist ja vor allem als Ersatz für den Cloud-Speicherdienst Dropbox bekannt.</p>
<p>Eine <a href="https://apps.nextcloud.com/apps/calendar">Kalenderfunktion</a> ist einfach als Erweiterung nachrüstbar, so dass ich meine Nextcloud-Instanz auch als Ersatz für den <em>Google Kalender</em> verwenden kann.</p>
<p>Für meine Nextcloud-Installation wollte ich keine eigene Subdomain anlegen. Stattdessen entschied ich mich dafür, Nextcloud in einen Unterordner zu installieren, so dass sie beispielsweise so zu finden ist: <code>https://meinetolle.seite/services/nextcloud</code></p>
<p>Die Installation per des <a href="https://download.nextcloud.com/server/installer/setup-nextcloud.php">Web-Installer</a> klappte ohne Probleme.</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01-2x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/c0d7e761f6-1681853444/01@2x.png" alt="Screenshot der NextCloud-Kalender-App mit einem geöffneten Dialog zum Bearbeiten eines Termins" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
<figcaption>
<p>Zum Testen habe ich einen neuen Event im Kalender erstellt.</p> </figcaption>
</figure>
</div><div>
<h2><a href="https://snorpey.codes/de#einrichtung-der-synchronisation-mit-der-mac-kalender-app">Einrichtung der Synchronisation mit der Mac Kalender App</a></h2>
<p>Die Oberfläche des Nextcloud Kalenders ist zwar funktional und gut bedienbar, ich verwalte meine Kalender aber lieber mit nativen Apps auf Mac und Android.</p>
<p>Die Synchronisation von Kalendern funktioniert in der Regel über <a href="https://en.wikipedia.org/wiki/CalDAV">CalDAV</a>. Der Nextcloud Kalender stellt dafür eine CalDAV-Adresse bereit. Diese kann ich in meinen nativen Apps hinzufügen, um die Kalenderdaten zu abgzugleichen.</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02-2x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/46037a5b38-1681853439/02@2x.png" alt="Screenshot der NextCloud-Kalender-App mit hervorgehobenen CalDAV-Einstellungen" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
<figcaption>
<p>Die CalDAV Adresse wird im Nextcloud Kalender unten links angezeigt.</p> </figcaption>
</figure>
</div><div>
<p>In der Mac Kalender App habe ich zunächst ein neues CalDAV Konto hinzugefügt, unter <code>Kalender > Konten</code>.</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03-2x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/1410f76392-1681853446/03@2x.png" alt="Screenshot des Einstellungsfelds für Internetkonten unter MacOS" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
</figure>
</div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04-2x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/6d80b436e0-1681853443/04@2x.png" alt="Screenshot des MacOS-Einstellungsfelds für Internetkonten mit hervorgehobener CalDAV-Option" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
</figure>
</div><div>
<p>Und jetzt hätte die Synchronisation <em>eigentlich</em> funktionieren sollen, allerdings schien die Mac Kalender App Probleme zu haben:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05-2x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/dde75f6564-1681853449/05@2x.png" alt="Screenshot eines MacOS-Popups mit dem Titel 'CalDAV-Konto hinzufügen' mit Eingabefeldern für Kontotyp, E-Mail-Adresse und Passwort" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
</figure>
</div><div>
<p>Nach längerer Recherche fand ich die Ursache des Problems: Die Mac Kalender App versucht, im Hauptverzeichnis der Servers folgende URLs aufzurufen:</p>
<ul>
<li><code>.well-known/caldav</code></li>
<li><code>.well-known/carddav</code></li>
</ul>
<p>Dieser Vorgang nennt sich <em>Service Discovery</em> und ist in <a href="http://tools.ietf.org/html/rfc5785">RFC5785</a> näher beschrieben.</p>
<p>Da meine Nextcloud Instanz in einem Unterordner und eben <strong>nicht</strong> im Hauptverzeichnis installiert ist, laufen diese Anfragen ins Leere.</p>
<p>Um dieses Problem zu beheben, muss auf dem Server eine Weiterleitung eingerichtet werden, welche die Anfragen in den Nextcloud-Unterordner umleitet.</p>
<p>Mein Hostinganbieter setzt (wie die meissten Anbieter von _<a href="https://en.wikipedia.org/wiki/Shared_web_hosting_service">Shared Hosting</a>_) den <a href="https://de.wikipedia.org/wiki/Apache_HTTP_Server">Apache Webserver</a> ein. Ich konnte deshalb relativ einfach eine Weiterleitung einrichten, indem ich eine <code>.htaccess</code> Datei im Hauptverzeichnis angelegt habe:</p></div><div>
<p>Nach längerer Recherche fand ich die Ursache des Problems: Die Mac Kalender App versucht, im Hauptverzeichnis der Servers folgende URLs aufzurufen:</p>
<ul>
<li><code>.well-known/caldav</code></li>
<li><code>.well-known/carddav</code></li>
</ul>
<p>Dieser Vorgang nennt sich <em>Service Discovery</em> und ist in <a href="http://tools.ietf.org/html/rfc5785">RFC5785</a> näher beschrieben.</p>
<p>Da meine Nextcloud Instanz in einem Unterordner und eben <strong>nicht</strong> im Hauptverzeichnis installiert ist, laufen diese Anfragen ins Leere.</p>
<p>Um dieses Problem zu beheben, muss auf dem Server eine Weiterleitung eingerichtet werden, welche die Anfragen in den Nextcloud-Unterordner umleitet.</p>
<p>Mein Hostinganbieter setzt (wie die meissten Anbieter von _<a href="https://en.wikipedia.org/wiki/Shared_web_hosting_service">Shared Hosting</a>_) den <a href="https://de.wikipedia.org/wiki/Apache_HTTP_Server">Apache Webserver</a> ein. Ich konnte deshalb relativ einfach eine Weiterleitung einrichten, indem ich eine <code>.htaccess</code> Datei im Hauptverzeichnis angelegt habe:</p></div><div>
<p>In der <code>.htaccess</code> Datei habe ich dann meine Umleitung eingetragen:</p></div><div>
<p>In der <code>.htaccess</code> Datei habe ich dann meine Umleitung eingetragen:</p></div><div>
<p>(Falls schon eine <code>.htaccess</code> Datei vorhanden ist, kann man die Weiterleitungen auch einfach nach die anderen Regeln anfügen, oder nach einer schon vorhandnen <code>RewriteEngine on</code> Zeile einfügen.)</p>
<p>Nachdem ich Änderungen per eingetragen und gespeichert habe, funktioniert das Einrichten eines neuen CalDAV Kontos und die Termine werden synchronisiert:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07-2x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/86fed0db01-1681853448/07@2x.png" alt="Screenshot der Mac Kalender-App" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
<figcaption>
<p>Der Termin wird nun richtig angezeigt.</p> </figcaption>
</figure>
</div><div>
<h2>Nextcloud Kalender mit Android-Geräten synchronisieren</h2>
<p>Die auf Android-Geräten standardmäßig installierte <em>Google Calendar</em> App beherrscht die synchronisation via CalDAV leider nicht (bzw. nur mit Kalendern, die auf Googles Servern liegen).</p>
<p>Deshalb werden für die Synchronisation des Nextcloud Kalenders mit einem Android-Gerät werden zwei kostenlose Apps benötigt:</p>
<p>Die Applikation <a href="https://www.davdroid.com/download/">DAVDroid</a> ermöglicht die Synchronisation des Kalenders mit Kalendern, die nicht an das eigene Google-Konto gebunden sind.</p>
<p>Die Applikation <a href="https://github.com/Etar-Group/Etar-Calendar">Etar</a> ist eine quelloffene Kalender-App, die den Kalender anzeigen und verwalten kann.</p>
<p>Nach der Installation der beiden Apps zunächst wird zunächst die Synchronisation in DAVDroid eingerichtet. Dazu habe ich auf den orangefarbenen Plus-Button ein neues Konto angelegt und dabei die Option mit <code>URL and Username</code> gewählt. Hier habe ich wieder die CalDAV URL meines Nextcloud Kalenders eingetragen:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08-4x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/66179e9913-1681853438/08@4x.png" alt="Screenshot einer Android-App zur Erstellung eines CalDAV-Kontos" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
</figure>
</div><div>
<p>Nach dem Erstellen des CalDAV Kontos habe ich nochmal einen Haken gesetzt, damit der Kalender auch synchronisiert wird:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09-4x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/420ab5b9dd-1681853447/09@4x.png" alt="Screenshot einer Android-App zur Erstellung eines CalDAV-Kontos" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
</figure>
</div><div>
<p>Danach wurden die Termine auch in Etar richtig synchronisiert und angezeigt:</p></div><div>
<figure>
<div>
<picture>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-80x.webp 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-120x.webp 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-240x.webp 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-320x.webp 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-480x.webp 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-600x.webp 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-720x.webp 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-800x.webp 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-880x.webp 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-920x.webp 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1024x.webp 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1080x.webp 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1156x.webp 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1200x.webp 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1440x.webp 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1680x.webp 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1920x.webp 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-2240x.webp 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-2680x.webp 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-3560x.webp 3560w" type="image/webp"></source>
<source sizes="(max-width: 1023px) 100vw, 880px" srcset="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-80x.avif 80w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-120x.avif 120w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-240x.avif 240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-320x.avif 320w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-480x.avif 480w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-600x.avif 600w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-720x.avif 720w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-800x.avif 800w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-880x.avif 880w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-920x.avif 920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1024x.avif 1024w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1080x.avif 1080w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1156x.avif 1156w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1200x.avif 1200w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1440x.avif 1440w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1680x.avif 1680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-1920x.avif 1920w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-2240x.avif 2240w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-2680x.avif 2680w, https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10-4x-3560x.avif 3560w" type="image/avif"></source>
<img src="https://snorpey.codes/media/pages/artikel/nextcloud-sync-in-unterordner/3600dfb7d0-1681853443/10@4x.png" alt="Screenshot einer Android-Kalender-App" data-sizes="(max-width: 1023px) 100vw, 880px" loading="lazy" decoding="async">
</picture>
</div>
</figure>
</div><div>
<p>Weitere Informationen zum Thema gibt es hier:</p>
<ul>
<li><a href="http://sabre.io/dav/service-discovery/">http://sabre.io/dav/service-discovery/</a></li>
<li><a href="https://docs.nextcloud.com/server/13/admin_manual/issues/general_troubleshooting.html#troubleshooting-contacts-calendar">https://docs.nextcloud.com/server/13/admin_manual/issues/general_troubleshooting.html#troubleshooting-contacts-calendar</a></li>
</ul></div>