diff --git a/projects/random/animated-typing/index.html b/projects/random/animated-typing/index.html new file mode 100644 index 0000000..2a1af1c --- /dev/null +++ b/projects/random/animated-typing/index.html @@ -0,0 +1,25 @@ + + + + + + + + Typewriter Animation Effect with HTML, CSS, and JavaScript + + + + + + +
+
+

+

+ +

+
+
+ + + \ No newline at end of file diff --git a/projects/random/animated-typing/readme.md b/projects/random/animated-typing/readme.md new file mode 100644 index 0000000..f5a872d --- /dev/null +++ b/projects/random/animated-typing/readme.md @@ -0,0 +1,4 @@ +Just some quick notes. This project comes from the video [Typewriter Animation Effect with HTML, CSS, and JavaScript](https://www.youtube.com/watch?v=7uDdiMCVwJk). It appealed because it was showing the sort of typing effect without using any frameworks, sdks or anything else. + + + diff --git a/projects/random/animated-typing/script.js b/projects/random/animated-typing/script.js new file mode 100644 index 0000000..3884cea --- /dev/null +++ b/projects/random/animated-typing/script.js @@ -0,0 +1,67 @@ +class Typewriter { + constructor(el, options) { + this.el = el; + this.words = [...this.el.dataset.typewriter.split(",")]; + + this.speed = options?.speed || 100; + this.delay = options?.delay || 1000; + this.repeat = options?.repeat || false; + + this.initTyping(); + } + + wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + toggleTyping = () => this.el.classList.toggle("typing"); + + async typewrite(word) { + await this.wait(this.delay); + this.toggleTyping(); + + for (const letter of word.split("")) { + this.el.textContent += letter; + await this.wait(this.speed); + } + + this.toggleTyping(); + await this.wait(this.delay); + + this.toggleTyping(); + while (this.el.textContent.length !== 0) { + this.el.textContent = this.el.textContent.slice(0, -1); + await this.wait(this.speed); + } + + this.toggleTyping(); + } + + async initTyping() { + for (const word of this.words) { + await this.typewrite(word); + } + + if (this.repeat) { + await this.initTyping(); + } else { + this.el.style.animation = "none"; + } + + // can't use forEach with await + // this.words.forEach((word) => { + // await this.typewrite(word); + // }); + } +} + +// const el1 = new Typewriter(document.querySelector("[data-typewriter]"), { +// speed: 10, +// delay: 10, +// repeat: true, +// }); +// console.log(el1); + +document.querySelectorAll("[data-typewriter]").forEach((el) => { + new Typewriter(el, { + repeat: true, + }); +}); diff --git a/projects/random/animated-typing/style.css b/projects/random/animated-typing/style.css new file mode 100644 index 0000000..ed38cab --- /dev/null +++ b/projects/random/animated-typing/style.css @@ -0,0 +1,44 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +main { + display: grid; + align-items: center; + padding: 4rem; + min-height: 100vh; + background: linear-gradient(#eff0f3, #efe0ef); +} + +.container { + display: grid; + justify-items: start; + gap: 2rem; +} + +[data-typewriter] { + font-family: system-UI; + font-weight: bold; + font-size: 4.5rem; + color: #d9376e; + height: 6rem; + border-right: 0.8rem solid transparent; + padding: 0.6rem; +} + +[data-typewriter]:not(.typing) { + animation: blink-cursor 1.1s step-end infinite; +} + +@keyframes blink-cursor { + 0%, + 100% { + border-color: transparent; + } + + 50% { + border-color: #ff8e3c; + } +} diff --git a/projects/random/using-template/css/style.css b/projects/random/using-template/css/style.css new file mode 100644 index 0000000..1dd3887 --- /dev/null +++ b/projects/random/using-template/css/style.css @@ -0,0 +1,35 @@ +.search-wrapper { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +input { + font-size: 1rem; +} + +.user-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 0.25rem; + margin-top: 1rem; +} + +.card { + border: 1px solid black; + background-color: whitesmoke; + padding: 0.5rem; +} + +.card > .name { + margin-bottom: 0.25rem; +} + +.card > .email { + font-size: 0.8rem; + columns: #777; +} + +.hide { + display: none; +} diff --git a/projects/random/using-template/index.html b/projects/random/using-template/index.html new file mode 100644 index 0000000..18b9125 --- /dev/null +++ b/projects/random/using-template/index.html @@ -0,0 +1,41 @@ + + + + + + + + + + Using Template with Search + + + +
+ +
+
+ + + +
+ + +
+ + +
+
+ +
+ + + + + + \ No newline at end of file diff --git a/projects/random/using-template/js/script.js b/projects/random/using-template/js/script.js new file mode 100644 index 0000000..a02ada3 --- /dev/null +++ b/projects/random/using-template/js/script.js @@ -0,0 +1,63 @@ +// * https://jsonplaceholder.typicode.com/users + +const userCardTemplate = document.querySelector("[data-user-template]"); +// console.log(userCardTemplate); + +const userCardsContainer = document.querySelector( + "[data-user-cards-container]" +); +// console.log(userCardsContainer); + +// Original code from video used the attribute for the query rather +// than the ID that he had setup. So I swapped it over. +// const searchInput = document.querySelector("[data-search]"); +const searchInput = document.querySelector("#search"); +// console.log(searchInput); + +let users = []; // filed from user data retrieved from server + +searchInput.addEventListener("input", (event) => { + const value = event.target.value.toLowerCase(); + // console.log(users); + users.forEach((user) => { + // this seems wasteful to run toLowerCase each time you type a letter + // surely better to just store a lowercase version when saving original data + // const isVisible = + // user.name.toLowerCase().includes(value) || + // user.email.toLowerCase().includes(value); + + const isVisible = + user.lowercaseName.includes(value) || + user.lowercaseEmail.includes(value); + + user.element.classList.toggle("hide", !isVisible); + }); +}); + +fetch("https://jsonplaceholder.typicode.com/users") + .then((res) => res.json()) + .then((data) => { + users = data.map((user) => { + const card = userCardTemplate.content.cloneNode(true).children[0]; + + const name = card.querySelector("[data-name]"); + const email = card.querySelector("[data-email]"); + + name.textContent = user.name; + email.textContent = user.email; + + // console.log(card); + + userCardsContainer.append(card); + + // Below is original return + // return { name: user.name, email: user.email, element: card }; + return { + name: user.name, + email: user.email, + lowercaseName: user.name.toLowerCase(), + lowercaseEmail: user.email.toLowerCase(), + element: card, + }; + }); + });