animations

This commit is contained in:
VividTruthKeeper 2023-02-18 19:35:22 +05:00
parent bb8c202928
commit 7f84792673
13 changed files with 298 additions and 114 deletions

View File

@ -4,7 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite --host",
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },

View File

@ -13,3 +13,52 @@ export const searchMotion: Variants = {
type: "tween", type: "tween",
}, },
}; };
export const searchMobileMotion: Variants = {
rest: {
width: "12rem",
right: "8rem",
type: "spring",
borderColor: "rgba(166, 166, 166, 0)",
},
active: {
width: "100%",
right: "0",
type: "spring",
borderColor: "rgba(166, 166, 166, 1)",
},
logoRest: {
display: "block",
opacity: 1,
transform: "translateX(0%)",
type: "spring",
},
logoActive: {
opacity: 0,
transform: "translateX(-150%)",
type: "spring",
},
langRest: {
opacity: 1,
transform: "translateX(0%)",
type: "spring",
},
langActive: {
opacity: 0,
transform: "translateX(150%)",
type: "spring",
},
loopRest: {
width: "1.3rem",
height: "1.4rem",
type: "spring",
},
loopActive: {
width: "2rem",
height: "2rem",
type: "spring",
},
};

View File

@ -20,12 +20,12 @@ const MainImg = () => {
<LazyLoadImage <LazyLoadImage
src={ src={
(data[0].featured_images.length > 0 (data[0].featured_images.length > 0
? data[0].featured_images[0] ? data[0].featured_images[0].path
: "") as string : "") as string
} }
alt={ alt={
(data[0].featured_images.length > 0 (data[0].featured_images.length > 0
? data[0].featured_images[0] ? data[0].featured_images[0].file_name
: "") as string : "") as string
} }
useIntersectionObserver useIntersectionObserver

View File

@ -70,7 +70,6 @@ const NewsScroll = ({ title, category }: Props) => {
return ( return (
<div className="news-scroll"> <div className="news-scroll">
<SectionTitle title="Лента новостей" />
<div className="news-scroll-wrapper"> <div className="news-scroll-wrapper">
{title === true ? ( {title === true ? (
<SectionTitle <SectionTitle

View File

@ -1,4 +1,5 @@
// Modules // Modules
import { Dispatch, SetStateAction } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { useState } from "react"; import { useState } from "react";
@ -16,6 +17,9 @@ import { ILanguage, RootState } from "../../types/store.types";
// Actions // Actions
import { setLanguage } from "../../actions/setLanguage"; import { setLanguage } from "../../actions/setLanguage";
// Animations
import { searchMobileMotion } from "../../animations/search.animation";
const languages: ILanguage[] = [ const languages: ILanguage[] = [
{ {
title: "RU", title: "RU",
@ -28,7 +32,12 @@ const languages: ILanguage[] = [
}, },
]; ];
const LanguageSelect = () => { interface IProps {
isSmall: boolean;
isInputFocused: boolean;
}
const LanguageSelect = ({ isSmall, isInputFocused }: IProps) => {
const [dropdown, setDropdown] = useState<boolean>(false); const [dropdown, setDropdown] = useState<boolean>(false);
const activeLanguage = useSelector<RootState, RootState["language"]["title"]>( const activeLanguage = useSelector<RootState, RootState["language"]["title"]>(
@ -41,48 +50,54 @@ const LanguageSelect = () => {
}; };
return ( return (
<motion.div <motion.div
className="language" initial={isSmall ? "langRest" : {}}
onClick={() => setDropdown(!dropdown)} animate={isSmall ? (isInputFocused ? "langActive" : "langRest") : {}}
initial={"wrapperRest"} variants={searchMobileMotion}
animate={dropdown ? "wrapperActive" : "wrapperRest"}
variants={languageMotion}
> >
<span>{activeLanguage}</span>
<motion.div <motion.div
className="icon-wrapper" className="language"
initial={"arrowRest"} onClick={() => setDropdown(!dropdown)}
animate={dropdown ? "arrowActive" : "arrowRest"} initial={"wrapperRest"}
animate={dropdown ? "wrapperActive" : "wrapperRest"}
variants={languageMotion} variants={languageMotion}
> >
<ArrowDownBlack /> <span>{activeLanguage}</span>
<motion.div
className="icon-wrapper"
initial={"arrowRest"}
animate={dropdown ? "arrowActive" : "arrowRest"}
variants={languageMotion}
>
<ArrowDownBlack />
</motion.div>
<motion.ul
className="language-dropdown"
variants={languageMotion}
initial="rest"
animate={dropdown ? "active" : "rest"}
>
{languages.map((language: ILanguage) => {
return (
<li key={uuidv4()}>
<motion.button
type="button"
initial={{
background: "#ffffff",
type: "tween",
}}
whileHover={{
background: "#f1f1f1",
type: "spring",
}}
onClick={() => onLanguageClick(language.title)}
>
{language.title}
</motion.button>
</li>
);
})}
</motion.ul>
</motion.div> </motion.div>
<motion.ul
className="language-dropdown"
variants={languageMotion}
initial="rest"
animate={dropdown ? "active" : "rest"}
>
{languages.map((language: ILanguage) => {
return (
<li key={uuidv4()}>
<motion.button
type="button"
initial={{
background: "#ffffff",
type: "tween",
}}
whileHover={{
background: "#f1f1f1",
type: "spring",
}}
onClick={() => onLanguageClick(language.title)}
>
{language.title}
</motion.button>
</li>
);
})}
</motion.ul>
</motion.div> </motion.div>
); );
}; };

View File

@ -1,5 +1,8 @@
// Modules // Modules
import { useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { motion } from "framer-motion";
// Icons // Icons
import { ReactComponent as Logo } from "../../assets/icons/logo.svg"; import { ReactComponent as Logo } from "../../assets/icons/logo.svg";
import { ReactComponent as Instagram } from "../../assets/icons/insta-black.svg"; import { ReactComponent as Instagram } from "../../assets/icons/insta-black.svg";
@ -10,48 +13,94 @@ import { ReactComponent as TikTok } from "../../assets/icons/tiktok-black.svg";
import Search from "./Search"; import Search from "./Search";
import LanguageSelect from "./LanguageSelect"; import LanguageSelect from "./LanguageSelect";
// Hooks
import useMediaQuery from "../../hooks/useMediaQuery";
// Animation
import { searchMobileMotion } from "../../animations/search.animation";
const Nav = () => { const Nav = () => {
const isSmall = useMediaQuery("(max-width: 850px)");
const [isInputFocused, setIsInputFocused] = useState<boolean>(false);
return ( return (
<nav className="nav"> <nav className="nav">
<div className="container"> <div className="container">
<div className="nav-inner"> {!isSmall ? (
<div className="nav-left"> <div className="nav-inner">
<Search /> <div className="nav-left">
<LanguageSelect /> <Search
isSmall={isSmall}
isInputFocused={isInputFocused}
setIsInputFocused={setIsInputFocused}
/>
<LanguageSelect
isSmall={isSmall}
isInputFocused={isInputFocused}
/>
</div>
<div className="nav-mid">
<Link to="/">
<Logo />
</Link>
</div>
<div className="nav-right">
<ul>
<li>
<a href="#" target={"_blank"}>
<Instagram />
<span>hhm@inst.com</span>
</a>
</li>
<li>
<a href="#" target={"_blank"}>
<Facebook />
<span>hhm@face.com</span>
</a>
</li>
<li>
<a href="#" target={"_blank"}>
<TikTok />
<span>@hhm</span>
</a>
</li>
</ul>
</div>
</div> </div>
<div className="nav-mid"> ) : (
<Link to="/"> <motion.div className="nav-inner mobile">
<Logo /> <motion.div
</Link> className="nav-mid"
</div> initial={isSmall ? "logoRest" : {}}
<div className="nav-right"> animate={
<ul> isSmall ? (isInputFocused ? "logoActive" : "logoRest") : {}
<li> }
<a href="#" target={"_blank"}> variants={searchMobileMotion}
<Instagram /> >
<span>hhm@inst.com</span> <Link to="/">
</a> <Logo />
</li> </Link>
<li> </motion.div>
<a href="#" target={"_blank"}> <motion.div
<Facebook /> className="search-wrap"
<span>hhm@face.com</span> initial={isSmall ? "rest" : {}}
</a> animate={isSmall ? (isInputFocused ? "active" : "rest") : {}}
</li> variants={searchMobileMotion}
<li> >
<a href="#" target={"_blank"}> <Search
<TikTok /> isSmall={isSmall}
<span>@hhm</span> isInputFocused={isInputFocused}
</a> setIsInputFocused={setIsInputFocused}
</li> />
</ul> </motion.div>
</div> <div className="lang-wrap">
<div className="nav-burger"> <LanguageSelect
<span className="line line-1"></span> isSmall={isSmall}
<span className="line line-2"></span> isInputFocused={isInputFocused}
<span className="line line-3"></span> />
</div> </div>
</div> </motion.div>
)}
</div> </div>
</nav> </nav>
); );

View File

@ -1,9 +1,13 @@
// Modules // Modules
import { Dispatch, SetStateAction } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
// Animations // Animations
import { searchMotion } from "../../animations/search.animation"; import {
searchMotion,
searchMobileMotion,
} from "../../animations/search.animation";
// Icons // Icons
import { ReactComponent as Loop } from "../../assets/icons/loop.svg"; import { ReactComponent as Loop } from "../../assets/icons/loop.svg";
@ -14,10 +18,17 @@ import { RootState } from "../../types/store.types";
// Actions // Actions
import { setSearch } from "../../actions/setSearch"; import { setSearch } from "../../actions/setSearch";
const Search = () => { interface IProps {
isSmall: boolean;
isInputFocused: boolean;
setIsInputFocused: Dispatch<SetStateAction<boolean>>;
}
const Search = ({ isSmall, isInputFocused, setIsInputFocused }: IProps) => {
const onInputChange = (value: string) => { const onInputChange = (value: string) => {
dispatch(setSearch(value)); dispatch(setSearch(value));
}; };
// redux // redux
const dispatch = useDispatch(); const dispatch = useDispatch();
const inputValue = useSelector<RootState, RootState["search"]["value"]>( const inputValue = useSelector<RootState, RootState["search"]["value"]>(
@ -37,14 +48,21 @@ const Search = () => {
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
onInputChange(event.target.value) onInputChange(event.target.value)
} }
onFocus={() => {
setIsInputFocused(true);
}}
onBlur={() => {
setIsInputFocused(false);
}}
/> />
<motion.div <motion.div
className="search-content"
initial={"rest"} initial={"rest"}
animate={inputValue.length > 0 ? "active" : "rest"} animate={inputValue.length > 0 ? "active" : "rest"}
variants={searchMotion} variants={searchMotion}
> >
<Loop /> <Loop />
<span>Search anything...</span> <span>Search</span>
</motion.div> </motion.div>
</form> </form>
); );

View File

@ -1,5 +1,5 @@
// Modules // Modules
import { Swiper, SwiperSlide } from "swiper/react"; import { Swiper, SwiperSlide, useSwiper } from "swiper/react";
import { Navigation, Pagination, Autoplay } from "swiper"; import { Navigation, Pagination, Autoplay } from "swiper";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
@ -13,12 +13,13 @@ interface IProps {
} }
const NewsArticleSlider = ({ images }: IProps) => { const NewsArticleSlider = ({ images }: IProps) => {
let loop = images.length > 1;
return ( return (
<div className="news-article-slider"> <div className="news-article-slider">
<Swiper <Swiper
spaceBetween={24} spaceBetween={24}
slidesPerView={1} slidesPerView={1}
loop loop={loop}
autoplay={{ autoplay={{
delay: 3000, delay: 3000,
disableOnInteraction: false, disableOnInteraction: false,

View File

@ -0,0 +1,22 @@
// Modules
import { useState, useEffect } from "react";
const useMediaQuery = (query: string): boolean => {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => {
setMatches(media.matches);
};
media.addListener(listener);
return () => media.removeListener(listener);
}, [matches, query]);
return matches;
};
export default useMediaQuery;

View File

@ -21,7 +21,7 @@ const Main = () => {
<div className="news-inner"> <div className="news-inner">
<MainContent /> <MainContent />
<div className="news-outer-wrapper"> <div className="news-outer-wrapper">
<NewsScroll title={false} /> <NewsScroll title={true} />
<Aside /> <Aside />
</div> </div>
<Videos /> <Videos />

View File

@ -1,5 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); @import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap'); @import url("https://fonts.googleapis.com/css2?family=Raleway&display=swap");
* { * {
margin: 0; margin: 0;
@ -16,6 +16,7 @@ html {
body { body {
min-height: 100vh; min-height: 100vh;
overflow-x: hidden;
} }
#root { #root {

View File

@ -1,4 +1,5 @@
.nav-inner { .nav-inner {
position: relative;
padding: 3rem 0; padding: 3rem 0;
display: grid; display: grid;
align-items: center; align-items: center;
@ -6,17 +7,44 @@
gap: 2.4rem; gap: 2.4rem;
justify-content: space-between; justify-content: space-between;
border-bottom: 0.1rem solid $black; border-bottom: 0.1rem solid $black;
z-index: 2;
&.mobile {
min-height: 10rem;
display: flex;
flex-direction: row;
align-items: center;
gap: 1.6rem;
justify-content: space-between;
.nav-mid {
position: absolute;
left: 0;
top: 3rem;
}
.search-wrap {
position: absolute;
right: 8rem;
top: 3rem;
}
.lang-wrap {
position: absolute;
right: 0;
top: 3rem;
}
}
} }
.search { .search {
width: 100%;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
border: 0.1rem solid $gray-dark;
border-radius: 0.5rem;
input { input {
border: 0.1rem solid $gray-dark;
padding: 1rem 1.4rem; padding: 1rem 1.4rem;
font-size: 1.6rem; font-size: 1.6rem;
color: $black; color: $black;
border-radius: 0.5rem;
} }
div { div {
@ -37,6 +65,7 @@
} }
.nav-left { .nav-left {
width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1.6rem; gap: 1.6rem;
@ -162,39 +191,34 @@
// Media // Media
@media screen and (max-width: 850px) { @media screen and (max-width: 850px) {
// .nav-burger {
// display: block;
// width: 3rem;
// height: 3rem;
// .line {
// position: absolute;
// width: 100%;
// border-radius: 1rem;
// background: $black;
// height: 0.33rem;
// &-1 {
// top: 15%;
// }
// &-2 {
// top: 50%;
// transform: translateY(-50%);
// }
// &-3 {
// top: 75%;
// }
// }
// }
.search,
.nav-right { .nav-right {
display: none; display: none;
} }
.nav-left { .nav-left {
justify-content: flex-end;
padding-right: 0; padding-right: 0;
} }
.search {
input {
width: 100%;
padding: 1rem 0.8rem;
}
div {
left: 0.8rem;
img {
width: 100%;
height: 100%;
}
span {
white-space: nowrap;
}
}
}
.nav-inner { .nav-inner {
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;

View File

@ -102,6 +102,12 @@
color: $black; color: $black;
} }
.news-image {
img {
@include wh100;
}
}
@media (max-width: 1024px) { @media (max-width: 1024px) {
.news-title { .news-title {
font-size: 2rem; font-size: 2rem;