update
This commit is contained in:
@@ -1,47 +1,24 @@
|
|||||||
import { watchEffect, watch, onMounted, type CSSProperties, defineComponent, ref } from "vue";
|
import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
|
||||||
|
import { h as djh } from "@/util.ts";
|
||||||
|
|
||||||
const carrierStyle = {
|
type TooltipContext = {
|
||||||
opacity: "0",
|
show: (newText: string, x: number, y: number) => void,
|
||||||
display: "block",
|
hide: () => void,
|
||||||
pointerEvents: "none",
|
};
|
||||||
backgroundColor: "black",
|
|
||||||
border: "white solid 1px",
|
|
||||||
color: "white",
|
|
||||||
padding: "10px",
|
|
||||||
position: "absolute",
|
|
||||||
zIndex: "1",
|
|
||||||
overflow: "hidden",
|
|
||||||
height: "0",
|
|
||||||
width: "0",
|
|
||||||
transition: "opacity 200ms, height 200ms, width 200ms",
|
|
||||||
} satisfies CSSProperties;
|
|
||||||
|
|
||||||
const textCarrierStyle = {
|
const tooltipContext = Symbol('tooltip') as InjectionKey<TooltipContext>;
|
||||||
fontSize: '16px',
|
|
||||||
fontFamily: "Roboto, serif",
|
|
||||||
display: "block",
|
|
||||||
overflow: "hidden",
|
|
||||||
} satisfies CSSProperties;
|
|
||||||
|
|
||||||
const defaultWidth = 350;
|
export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
|
||||||
|
const { carrier } = options;
|
||||||
|
|
||||||
export default defineComponent({
|
watchEffect(() => {
|
||||||
name: "dj-sexy-tooltip",
|
if (carrier.value) {
|
||||||
props: {
|
carrier.value.classList.add('tooltip-carrier');
|
||||||
tooltip: {
|
carrier.value.appendChild(djh('div', { className: 'text-carrier' }));
|
||||||
type: String,
|
}
|
||||||
required: true,
|
});
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { slots, attrs }) {
|
|
||||||
const active = ref(false);
|
|
||||||
|
|
||||||
const carrier = ref<HTMLElement | null>(null);
|
const listener = (pos: { x: number, y: number }) => {
|
||||||
const textCarrier = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener("mousemove", (event) => {
|
|
||||||
const pos = { x: event.pageX, y: event.pageY };
|
|
||||||
if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") {
|
if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") {
|
||||||
if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) {
|
if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) {
|
||||||
carrier.value.style.left = (pos.x + 15) + "px";
|
carrier.value.style.left = (pos.x + 15) + "px";
|
||||||
@@ -54,27 +31,64 @@ export default defineComponent({
|
|||||||
carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px";
|
carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
const active = ref(false);
|
||||||
if (carrier.value) {
|
|
||||||
carrier.value.style.height = active.value ? '16px' : '0';
|
watch(active, async () => {
|
||||||
carrier.value.style.opacity = active.value ? '1' : '0';
|
const tooltipCarrier = carrier.value;
|
||||||
carrier.value.style.width = active.value ? '350px' : '0';
|
if (tooltipCarrier) {
|
||||||
|
tooltipCarrier.style.opacity = active.value ? '1' : '0';
|
||||||
|
tooltipCarrier.style.width = active.value ? '350px' : '0';
|
||||||
|
await nextTick();
|
||||||
|
if (active.value) {
|
||||||
|
if (tooltipCarrier.firstChild?.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
const computedHeight = getComputedStyle(tooltipCarrier.firstChild as Element).height;
|
||||||
|
tooltipCarrier.style.height = computedHeight;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tooltipCarrier.style.height = '0';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(() => document.addEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
|
||||||
|
onBeforeUnmount(() => document.removeEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
|
||||||
|
|
||||||
|
const ctx: TooltipContext = {
|
||||||
|
show(tooltip, x, y) {
|
||||||
|
if (carrier.value) {
|
||||||
|
carrier.value.firstChild!.textContent = tooltip;
|
||||||
|
}
|
||||||
|
active.value = true;
|
||||||
|
listener({ x, y });
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
active.value = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
provide(tooltipContext, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "dj-tooltip",
|
||||||
|
props: {
|
||||||
|
tooltip: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { slots, attrs }) {
|
||||||
|
const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true);
|
||||||
|
|
||||||
return () => <>
|
return () => <>
|
||||||
<div class="tooltip-container" {...attrs}
|
<div class="tooltip-container"
|
||||||
onMouseenter={() => { active.value = true; }}
|
{...attrs}
|
||||||
onMouseleave={() => { active.value = false; }}
|
onMouseenter={(e) => tooltip.show(props.tooltip, e.pageX, e.pageY)}
|
||||||
>
|
onMouseleave={() => tooltip.hide()}>
|
||||||
{slots.default && <slots.default />}
|
{slots.default && <slots.default />}
|
||||||
</div>
|
</div>
|
||||||
<div style={carrierStyle} ref={carrier}>
|
|
||||||
<span style={textCarrierStyle}>{props.tooltip}</span>
|
|
||||||
</div>
|
|
||||||
</>;
|
</>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,50 @@
|
|||||||
import { defineComponent } from "vue";
|
import { defineComponent, computed, ref, type Ref } from "vue";
|
||||||
import useHead from "@/useHead.ts";
|
import useHead from "@/useHead.ts";
|
||||||
import DJTooltip from "@/DJTooltip.tsx";
|
import DJTooltip, { setupTooltip } from "@/DJTooltip.tsx";
|
||||||
import DJEmail from "@/DJEmail.tsx";
|
import DJEmail from "@/DJEmail.tsx";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "app-root",
|
name: "app-root",
|
||||||
setup() {
|
setup() {
|
||||||
useHead({ title: "DJ Ledda's Homepage" });
|
useHead({ title: "DJ Ledda's Homepage" });
|
||||||
return () => (
|
|
||||||
|
const tooltipCarrier = ref<HTMLDivElement | null>(null);
|
||||||
|
setupTooltip({ carrier: tooltipCarrier });
|
||||||
|
|
||||||
|
const dude1Spinning = ref(false);
|
||||||
|
const dude2Spinning = ref(false);
|
||||||
|
|
||||||
|
function toggleDude(event: MouseEvent, dudeRef: Ref<boolean>) {
|
||||||
|
const dude = event.target as HTMLImageElement;
|
||||||
|
if (dudeRef.value) {
|
||||||
|
dude.addEventListener("animationiteration", function listener() {
|
||||||
|
dudeRef.value = false;
|
||||||
|
dude.removeEventListener("animationiteration", listener as EventListenerOrEventListenerObject);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dudeRef.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shaking = computed(() => dude1Spinning.value || dude2Spinning.value);
|
||||||
|
|
||||||
|
return () => <>
|
||||||
|
<div ref={tooltipCarrier} class="tooltip-carrier" />
|
||||||
<div class="supercontainer">
|
<div class="supercontainer">
|
||||||
<div class="shakeable">
|
<div class={{ shakeable: true, shakeMe: shaking.value }}>
|
||||||
<div class="title_name">
|
<div class="title_name">
|
||||||
<DJTooltip tooltip="I wonder what he's listening to?">
|
<DJTooltip tooltip="I wonder what he's listening to?">
|
||||||
<img src="/home/img/dj.gif" alt="dj legt krasse Mucke auf" class="dude" />
|
<img src="/home/img/dj.gif" alt="dj legt krasse Mucke auf"
|
||||||
|
class={{ dude: true, spinMe: dude1Spinning.value }}
|
||||||
|
onClick={ (e) => toggleDude(e, dude1Spinning)} />
|
||||||
</DJTooltip>
|
</DJTooltip>
|
||||||
<DJTooltip tooltip="Easily the coolest guy out there.">
|
<DJTooltip tooltip="Easily the coolest guy out there.">
|
||||||
<span>DJ Ledda</span>
|
<span>DJ Ledda</span>
|
||||||
</DJTooltip>
|
</DJTooltip>
|
||||||
<DJTooltip tooltip="I once heard this guy played at revs.">
|
<DJTooltip tooltip="I once heard this guy played at revs.">
|
||||||
<img src="/home/img/dj.gif" alt="dj laying down some sick beats" class="dude" />
|
<img src="/home/img/dj.gif" alt="dj laying down some sick beats"
|
||||||
|
class={{ dude: true, spinMe: dude2Spinning.value }}
|
||||||
|
onClick={ (e) => toggleDude(e, dude2Spinning) } />
|
||||||
</DJTooltip>
|
</DJTooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
@@ -71,6 +97,6 @@ export default defineComponent({
|
|||||||
<div id="tooltipCarrier"></div>
|
<div id="tooltipCarrier"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createSSRApp } from "vue";
|
import { createSSRApp } from "vue";
|
||||||
import App from "@/home/App.tsx";
|
import DJHomeRoot from "@/home/DJHomeRoot.tsx";
|
||||||
|
|
||||||
createSSRApp(App).mount("#app-root");
|
createSSRApp(DJHomeRoot).mount("#app-root");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createSSRApp } from "vue";
|
import { createSSRApp } from "vue";
|
||||||
import App from "@/home/App.tsx";
|
import DJHomeRoot from "@/home/DJHomeRoot.tsx";
|
||||||
|
|
||||||
export default function createApp() {
|
export default function createApp() {
|
||||||
const app = createSSRApp(App);
|
const app = createSSRApp(DJHomeRoot);
|
||||||
return { app, router: null };
|
return { app, router: null };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export function css(strs: TemplateStringsArray, ...vals: string[]) {
|
|||||||
return sheet;
|
return sheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
export class DJElement extends HTMLElement {
|
export class DJElement extends HTMLElement {
|
||||||
static styles: CSSStyleSheet;
|
static styles: CSSStyleSheet;
|
||||||
|
|
||||||
@@ -57,3 +58,4 @@ export class DJElement extends HTMLElement {
|
|||||||
this.root.adoptedStyleSheets = statics.styles ? [statics.styles] : [];
|
this.root.adoptedStyleSheets = statics.styles ? [statics.styles] : [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
interface Point {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Size {
|
|
||||||
height: string;
|
|
||||||
width: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SexyTooltip {
|
|
||||||
private static readonly carrierStyle: Partial<CSSStyleDeclaration> = {
|
|
||||||
opacity: "0",
|
|
||||||
display: "block",
|
|
||||||
pointerEvents: "none",
|
|
||||||
backgroundColor: "black",
|
|
||||||
border: "white solid 1px",
|
|
||||||
color: "white",
|
|
||||||
padding: "10px",
|
|
||||||
position: "absolute",
|
|
||||||
zIndex: "1",
|
|
||||||
overflow: "hidden",
|
|
||||||
height: "0",
|
|
||||||
width: "0",
|
|
||||||
transition: "opacity 200ms, height 200ms, width 200ms",
|
|
||||||
};
|
|
||||||
private static readonly textCarrierStyle: Partial<CSSStyleDeclaration> = {
|
|
||||||
width: "350px",
|
|
||||||
display: "block",
|
|
||||||
overflow: "hidden",
|
|
||||||
};
|
|
||||||
private static readonly defaultWidth = 350;
|
|
||||||
private readonly carrier: HTMLDivElement;
|
|
||||||
private readonly textCarrier: HTMLSpanElement;
|
|
||||||
private readonly elementsWithTooltips: Element[] = [];
|
|
||||||
private active: boolean = false;
|
|
||||||
|
|
||||||
constructor(carrier: HTMLDivElement) {
|
|
||||||
if (carrier.childNodes.length > 0) {
|
|
||||||
throw new Error("Incorrect setup for tooltip! Remove the child nodes from the tooltip div.");
|
|
||||||
}
|
|
||||||
this.carrier = carrier;
|
|
||||||
this.textCarrier = document.createElement("span");
|
|
||||||
this.carrier.appendChild(this.textCarrier);
|
|
||||||
Object.assign(this.carrier.style, SexyTooltip.carrierStyle);
|
|
||||||
Object.assign(this.textCarrier.style, SexyTooltip.textCarrierStyle);
|
|
||||||
document.querySelectorAll(".tooltip").forEach((element) => {
|
|
||||||
if (element.nodeName === "SPAN") {
|
|
||||||
console.log(element.previousElementSibling);
|
|
||||||
this.elementsWithTooltips.push(element.previousElementSibling);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.addEventListener("mousemove", (event) => this.rerenderTooltip({ x: event.pageX, y: event.pageY }));
|
|
||||||
}
|
|
||||||
|
|
||||||
rerenderTooltip(mousePos: Point) {
|
|
||||||
const newText = this.getTooltipTextForHoveredElement(mousePos);
|
|
||||||
if (newText !== this.textCarrier.innerHTML) {
|
|
||||||
this.transitionTooltipSize(newText);
|
|
||||||
const tooltipHasText = newText !== "";
|
|
||||||
if (tooltipHasText !== this.active) {
|
|
||||||
this.toggleActive();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.isStillVisible()) {
|
|
||||||
this.updatePosition(mousePos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isStillVisible() {
|
|
||||||
return getComputedStyle(this.carrier).opacity !== "0";
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePosition(pos: Point) {
|
|
||||||
if (pos.x + 15 + this.carrier.clientWidth <= document.body.scrollWidth) {
|
|
||||||
this.carrier.style.left = (pos.x + 15) + "px";
|
|
||||||
} else {
|
|
||||||
this.carrier.style.left = (document.body.scrollWidth - this.carrier.clientWidth - 5) + "px";
|
|
||||||
}
|
|
||||||
if (pos.y + this.carrier.clientHeight <= document.body.scrollHeight) {
|
|
||||||
this.carrier.style.top = pos.y + "px";
|
|
||||||
} else {
|
|
||||||
this.carrier.style.top = (document.body.scrollHeight - this.carrier.clientHeight - 5) + "px";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleActive() {
|
|
||||||
if (this.active) {
|
|
||||||
this.active = false;
|
|
||||||
this.carrier.style.opacity = "0";
|
|
||||||
this.carrier.style.padding = "0";
|
|
||||||
} else {
|
|
||||||
this.active = true;
|
|
||||||
this.carrier.style.opacity = "1";
|
|
||||||
this.carrier.style.padding = SexyTooltip.carrierStyle.padding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transitionTooltipSize(text: string) {
|
|
||||||
const testDiv = document.createElement("div");
|
|
||||||
testDiv.style.height = "auto";
|
|
||||||
testDiv.style.width = SexyTooltip.defaultWidth + "px";
|
|
||||||
testDiv.innerHTML = text;
|
|
||||||
document.body.appendChild(testDiv);
|
|
||||||
const calculatedHeight = testDiv.scrollHeight;
|
|
||||||
testDiv.style.display = "inline";
|
|
||||||
testDiv.style.width = "auto";
|
|
||||||
document.body.removeChild(testDiv);
|
|
||||||
const size = { height: calculatedHeight + "px", width: "350px" };
|
|
||||||
this.carrier.style.height = size.height;
|
|
||||||
this.carrier.style.width = text === "" ? "0" : size.width;
|
|
||||||
this.textCarrier.innerHTML = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTooltipTextForHoveredElement(mousePos: Point): string {
|
|
||||||
for (const elem of this.elementsWithTooltips) {
|
|
||||||
const boundingRect = elem.getBoundingClientRect();
|
|
||||||
const inYRange = mousePos.y >= boundingRect.top + window.pageYOffset - 1 &&
|
|
||||||
mousePos.y <= boundingRect.bottom + window.pageYOffset + 1;
|
|
||||||
const inXRange = mousePos.x >= boundingRect.left + window.pageXOffset - 1 &&
|
|
||||||
mousePos.x <= boundingRect.right + window.pageXOffset + 1;
|
|
||||||
if (inYRange && inXRange) {
|
|
||||||
return (elem.nextElementSibling as HTMLSpanElement)?.innerText ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SexyTooltip;
|
|
||||||
@@ -166,7 +166,7 @@ span.subjecttitle {
|
|||||||
padding: 10px 20px 10px 20px;
|
padding: 10px 20px 10px 20px;
|
||||||
display: block;
|
display: block;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
text-decoration: none;
|
text-decorAtion: none;
|
||||||
transition: background-color 200ms;
|
transition: background-color 200ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,3 +187,31 @@ a {
|
|||||||
.tooltip-container {
|
.tooltip-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-carrier {
|
||||||
|
opacity: 0;
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: black;
|
||||||
|
border: white solid 1px;
|
||||||
|
color: white;
|
||||||
|
padding: 10px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
transition: opacity 200ms, height 200ms, width 200ms;
|
||||||
|
|
||||||
|
.text-carrier {
|
||||||
|
position: absolute;
|
||||||
|
width: 350px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Roboto", serif;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user