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",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "tsc && vite build",
"preview": "vite preview"
},

View File

@ -13,3 +13,52 @@ export const searchMotion: Variants = {
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
src={
(data[0].featured_images.length > 0
? data[0].featured_images[0]
? data[0].featured_images[0].path
: "") as string
}
alt={
(data[0].featured_images.length > 0
? data[0].featured_images[0]
? data[0].featured_images[0].file_name
: "") as string
}
useIntersectionObserver

View File

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

View File

@ -1,4 +1,5 @@
// Modules
import { Dispatch, SetStateAction } from "react";
import { motion } from "framer-motion";
import { v4 as uuidv4 } from "uuid";
import { useState } from "react";
@ -16,6 +17,9 @@ import { ILanguage, RootState } from "../../types/store.types";
// Actions
import { setLanguage } from "../../actions/setLanguage";
// Animations
import { searchMobileMotion } from "../../animations/search.animation";
const languages: ILanguage[] = [
{
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 activeLanguage = useSelector<RootState, RootState["language"]["title"]>(
@ -40,6 +49,11 @@ const LanguageSelect = () => {
dispatch(setLanguage(title));
};
return (
<motion.div
initial={isSmall ? "langRest" : {}}
animate={isSmall ? (isInputFocused ? "langActive" : "langRest") : {}}
variants={searchMobileMotion}
>
<motion.div
className="language"
onClick={() => setDropdown(!dropdown)}
@ -84,6 +98,7 @@ const LanguageSelect = () => {
})}
</motion.ul>
</motion.div>
</motion.div>
);
};

View File

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

View File

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

View File

@ -1,5 +1,5 @@
// Modules
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper, SwiperSlide, useSwiper } from "swiper/react";
import { Navigation, Pagination, Autoplay } from "swiper";
import { v4 as uuidv4 } from "uuid";
@ -13,12 +13,13 @@ interface IProps {
}
const NewsArticleSlider = ({ images }: IProps) => {
let loop = images.length > 1;
return (
<div className="news-article-slider">
<Swiper
spaceBetween={24}
slidesPerView={1}
loop
loop={loop}
autoplay={{
delay: 3000,
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">
<MainContent />
<div className="news-outer-wrapper">
<NewsScroll title={false} />
<NewsScroll title={true} />
<Aside />
</div>
<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=Raleway&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Raleway&display=swap");
* {
margin: 0;
@ -16,6 +16,7 @@ html {
body {
min-height: 100vh;
overflow-x: hidden;
}
#root {

View File

@ -1,4 +1,5 @@
.nav-inner {
position: relative;
padding: 3rem 0;
display: grid;
align-items: center;
@ -6,17 +7,44 @@
gap: 2.4rem;
justify-content: space-between;
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 {
width: 100%;
overflow: hidden;
position: relative;
input {
border: 0.1rem solid $gray-dark;
border-radius: 0.5rem;
input {
padding: 1rem 1.4rem;
font-size: 1.6rem;
color: $black;
border-radius: 0.5rem;
}
div {
@ -37,6 +65,7 @@
}
.nav-left {
width: 100%;
display: flex;
align-items: center;
gap: 1.6rem;
@ -162,39 +191,34 @@
// Media
@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 {
display: none;
}
.nav-left {
justify-content: flex-end;
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 {
display: flex;
flex-direction: row-reverse;

View File

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