页面设计

页面设计

项目 GitHub 地址:group_website

方法+重难点

CSS脱离文档流后同级元素上移问题

参考链接:css固定定位脱离文档流怎么解决?
某元素脱离文档流后,同级元素位置上移的原因是因为没有给父级层设置相应的高度。当子级元素没有脱离文档流时,父DIV会因为子DIV的高度而被撑起来,然而当子DIV设置绝对定位或固定定位时,父DIV不会再根据子DIV的高度撑起来,也就是我们常说的脱离文档流。
解决方法1:强行给父级DIV设置相应的高度,这个方案的缺点是不够灵活,需要自己计算要设置的高度

1
2
3
4
5
6
7
8
9
10
11
.parent{
/*解决方法1:css强制设置父模块宽度高度*/
height: 200px;
...
}
.son{
/*设置固定定位*/
position:fixed;
width: 200px;
height: 200px;
}

解决方法2:在合适的位置设置占位元素。没有在父元素设置元素高度直观,不建议采用。
解决方法3:使用JS设置父级DIV的高度等于子DIV (推荐)

1
2
3
4
5
6
window.onload = ()=>(
let son = document.getElementById('son');
let parent = document.getElementById('parent');
//不兼容IE9以前的浏览器,IE9 以前用 son.currentStyle 获取
parent.style.height = window.getComputedStyle('son').height;
)

window.getComputedStyle(): 以对象形式返回目标的所有CSS样式值
window.onload(): MDN文档中说明,onload 事件触发发生在所有 DOM 资源加载完成后,此时所有元素在DOM中均已完成挂载,不用担心 onload 事件触发后调用函数无作用对象的问题。
在 React 中用 ref 属性来获取 dom 元素进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//在组件完成挂载时,添加 window.onload 监听事件,它在所有 DOM 资源加载完成后调用函数执行。
componentDidMount() {
window.onload = ()=>{
this.parent.style.height = window.getComputedStyle(this.son).height
}
}
...
<div
style={{ position: "relative" }}
//获取DOM元素
ref={(parent) => { this.parent = parent }}
>
<img
style={{ position: "absolute", zIndex: -1, width: "100vw", height: "1080px" }}
src={headerPng}
alt=""
//获取DOM元素
ref={(son) => { this.son = son }}
/>
<Titles />
<DropdownMenu isActive={this.state.isActive} />
</div>
<div style={{ position: "relative" }}>
//去除了占位元素: - <div className="titlePlaceHolder"></div>
<PageOne />
</div>

渲染结果:

this 指向问题

参考链接:
this 指向问题
this 指向详细解析
这里只对实战中遇到的 this 做简要分析,具体的需要详细学习。
DOM事件处理函数
(1)当函数被当做监听事件处理函数时, 其 this 指向触发该事件的元素 (针对于addEventListener事件)
(2)代码被内联处理函数调用时,它的this指向监听器所在的DOM元素,当代码被包括在函数内部执行时,其this指向等同于函数直接调用的情况

setTimeout & setInterval
对于延时函数内部的回调函数的this指向全局对象window

箭头函数中的 this
(1)由于箭头函数不绑定this, 它会捕获其所在上下文的this值, 作为自己的this值。因此常被用于调用函数,改变函数的this指向,不容易出错。
(2)方法的箭头函数this指向全局window对象,而普通函数则指向调用它的对象,箭头函数没有this

举例:

1
setTimeout(() => { dispatch({ type: "close_isActive_delay" }) }, 300)

上述例子中,不能直接使用dispatch函数,因为在setTimeout中this指向全局对象window,使用箭头函数后自动关联上下文,this最终指向监听事件的DOM元素。(正确性有待商榷,对this不是很了解)

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
├─ group_website
│ └─ src
│ ├─ App.css
│ ├─ components
│ │ ├─ DropdownMenu.js
│ │ ├─ DropdownSubTitle.js
│ │ ├─ DropdownTitle.js
│ │ ├─ page1
│ │ │ ├─ PageOne.js
│ │ │ └─ PageOneText.js
│ │ ├─ SubTitle.js
│ │ └─ Titles.js
│ ├─ images
│ │ ├─ header.png
│ │ ├─ pgoneBackground.png
│ │ └─ testImg.jpg
│ ├─ index.js
│ ├─ store
│ │ ├─ index.js
│ │ └─ reducer.js
│ ├─ style
│ │ ├─ DropdownMenu.css
│ │ ├─ DropdownTitle.css
│ │ ├─ page1
│ │ │ └─ PageOne.css
│ │ ├─ SubTitle.css
│ │ └─ Titles.css
│ └─ Website.js
└─ package-lock.json

项目入口 js 文件为 Website.js,CSS样式存储于 /store文件夹下,页面组件存储于/components文件夹中。

下拉菜单

Website.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import React, { Component } from 'react';
import DropdownMenu from './components/DropdownMenu'
import Titles from './components/Titles'
import "./App.css"
import store from './store/index'
import headerPng from './images/header.png'
import PageOne from './components/page1/PageOne'

class Website extends Component {
constructor(props) {
super(props);
this.state = store.getState()
this.storeChange = this.storeChange.bind(this)
store.subscribe(this.storeChange)
}

storeChange() {
this.setState(store.getState())
}

render() {
return (
<div>
<div>
<div style={{ position: "relative" }}>
<img style={{position:"absolute",zIndex:-1,width:"100vw",height:"1080px"}} src={headerPng} alt="" />
<Titles />
<DropdownMenu isActive={this.state.isActive} />
</div>
<div style={{position:"relative"}}>
<div className="titlePlaceHolder"></div>
<PageOne />
</div>
</div>
</div>
);
}
}

export default Website;

/*
z-index设置:
1. 设置z-index的元素必须设置position:(relative/fixed/absolute),若不对位置有调整或特殊要求,可以单设一个relative。
2. 将要在z轴排序的元素用<div>包裹,同时在父级元素上也要设position。
3. 根据需要对不同的子元素设置z-index,排序。
***关键:所有参与的元素(包括父元素)都要设置position
*/

/*
脱离文档流后元素上移问题:
1. 同级元素脱离文档流后,在整体布局上不占位置。
2. 若要保证其他元素原有布局,用<div>创建一个具有原同级元素大小的元素充当占位元素。
*/

重难点分析

页面布局

  1. 如何让组件脱离文档流?方法一:设置position:("fixed"/"absolute"),方法二:设置display:float,不过目前常用的是 flex 布局,一般用不到 float 浮动布局。
  2. 脱离文档流的好处和坏处?好处:脱离文档流可以释放元素占据的空间,让同级以及子级元素浮动到顶部,在设置图片背景的时候常用,如实战代码中的<img style={{position:"absolute",zIndex:-1,width:"100vw",height:"1080px"}} src={headerPng} alt="" />。坏处:脱离文档流后后续的布局会被打乱,很难调整。
  3. 如何克服脱离文档流后后续元素上移问题:用<div>创建一个具有原同级元素大小的元素充当占位元素,或者给父级元素设置相应的高度。实战中代码使用如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //设置position:"absolute"后脱离文档流
    <div style={{ position: "relative" }}>
    <img style={{position:"absolute",zIndex:-1,width:"100vw",height:"1080px"}} src={headerPng} alt="" />
    <Titles />
    <DropdownMenu isActive={this.state.isActive} />
    </div>
    <div style={{position:"relative"}}>
    //设置一个div元素,大小与上述div最大尺寸相同,占据原有位置
    <div className="titlePlaceHolder"></div>
    <PageOne />
    </div>
  4. 脱离文档流的元素位置设置:position:("fixed"/"absolute")属性都是相对于父级的position:"relative"进行定位的。若要进行位置定位,在父级元素单独设置position:"relative"即可。
  5. 关于 z-index 属性设置:在设置背景的时候,往往会遇到背景遮盖了优先需要显示的部分,这就需要调整 z-index 的属性,z-index 越大,显示的优先级越高。
    1
    2
    3
    4
    5
    z-index设置:
    设置z-index的(每一个)元素必须设置position:(relative/fixed/absolute),若不对位置有调整或特殊要求,可以单设一个relative。
    将要在z轴排序的元素用<div>包裹,同时在父级元素上也要设position
    根据需要对不同的子元素设置z-index,排序。
    ***关键:所有参与的元素(包括父元素)都要设置position

Title.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import React, { Component } from 'react';
import "../style/Titles.css"
import SubTitle from './SubTitle';

class Titles extends Component {
constructor(props) {
super(props);
this.state = {}
}

render() {
const titleInfo = [
{
titleText: "Title1",
id:1,
subTitleProps: [
{ name: "百度", href: "https://www.baidu.com/", },
{ name: "搜狗", href: "https://www.sogou.com/", },
{ name: "360搜索", href: "https://www.so.com/", },
{ name: "火狐", href: "https://start.firefoxchina.cn/", },
],
},
{
titleText: "Title2",
id:2,
subTitleProps: [
{ name: "百度", href: "https://www.baidu.com/", },
{ name: "搜狗", href: "https://www.sogou.com/", },
{ name: "360搜索", href: "https://www.so.com/", },
{ name: "火狐", href: "https://start.firefoxchina.cn/", },
],
},
{
titleText: "Title3",
id:3,
subTitleProps: [
{ name: "百度", href: "https://www.baidu.com/", },
{ name: "搜狗", href: "https://www.sogou.com/", },
{ name: "360搜索", href: "https://www.so.com/", },
{ name: "火狐", href: "https://start.firefoxchina.cn/", },
],
},
{
titleText: "Title4",
id:4,
subTitleProps: [
{ name: "百度", href: "https://www.baidu.com/", },
{ name: "搜狗", href: "https://www.sogou.com/", },
{ name: "360搜索", href: "https://www.so.com/", },
{ name: "火狐", href: "https://start.firefoxchina.cn/", },
],
},
]

return (
<div>
<div className="titleContainer">
<div className="leftPlaceHolder"></div>
<div className="rightPlaceHolder">
{
titleInfo.map((item, index) => {
return (
<SubTitle
key={index}
titleText={item.titleText}
params={item.subTitleProps}
index={item.id}
/>)
})
}
</div>
</div>
</div>
);
}
}

export default Titles;

重难点分析

组件使用思想
React 尽量将具有相同功能的组件单独编写,一些内容变化可以通过传参的方式,事先声明存储内容的参数数组进行遍历,如上述代码所示。若功能完全一致的组件可以声明为高阶组件进行复用。

1
2
3
4
5
6
7
8
9
titleInfo.map((item, index) => {
return (
<SubTitle
key={index}
titleText={item.titleText}
params={item.subTitleProps}
index={item.id}
/>)
})
  1. 注意 {}是需要用return()返回内容的,这里也可以用()=>()的ES6写法,默认省略return,自动返回括号内的内容。
  2. 通过传递props的方式修改通用组件<SubTitle />中的部分内容,实现组件的高度复用。
  3. 页面布局的小技巧:
    1
    2
    <div className="leftPlaceHolder"></div>
    <div className="rightPlaceHolder">
    布局前实现用空<div>进行空间分配,再在相应空间内进行布局。

SubTitle.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import React, { Component } from 'react';
import DropdownTitle from './DropdownTitle'
import "../style/SubTitle.css"
import store from '../store/index';

class SubTitle extends Component {
constructor(props) {
super(props);
this.state = store.getState()
this.storeChange = this.storeChange.bind(this)
store.subscribe(this.storeChange)

this.handleMouseOver = this.handleMouseOver.bind(this)
this.handleMouseOut = this.handleMouseOut.bind(this)
}

handleMouseOver() {
const action = {
type: "open_isActive_delay",
id: this.props.index,
}
store.dispatch(action)
}

handleMouseOut() {
const delayAction = () => (dispatch, getState) => {
// setTimeout里用箭头函数调用dispatch函数,目的是绑定this,这里不能用bind方法,因为这样绑定的this是全局的,无效
let timer = setTimeout(() => { dispatch({ type: "close_isActive_delay" }) }, 300)
console.log(this.state.isActive)
dispatch({ type: "add_timer", timer })
}
store.dispatch(delayAction())
}

storeChange() {
this.setState(store.getState())
}

render() {
return (
<div className="subTitleContainer">
<div>
<a
className="textFont"
onMouseOver={(e) => {
this.handleMouseOver()
}}
onMouseOut={(e) => {
this.handleMouseOut()
}}
//this.state.id是鼠标移动到某一title时传递的id
//this.props.index是组件遍历渲染时分配的id
//即当两者相匹配时,证明该title被鼠标激活,此时修改样式
//将其他title从白色设为灰色,同时将激活的title设为天蓝色
style={
this.state.isActive ?
((this.state.id === this.props.index) ?{ color: "#44DAFF" } : { color: "#666666" })
: { color: "white" }
}
>{this.props.titleText}</a>
</div>
<DropdownTitle
params={this.props.params}
index={this.props.index}
/>
</div>
);
}
}

export default SubTitle;

/*
redux中怎么使用setTimeout? redux-thunk中间件
此处用setTimeout()做延迟操作,使用户有足够时间移动到子选项
注意omMouseOut里面的异步操作,setTimeout执行时,会执行this.setState(),而非等待setTimeout执行完。
*/

重难点分析

Redux-thunk
在导航栏设计时需要考虑用户拖动鼠标的时间,假设不设置 setTimeout 延时,会导致用户鼠标离开标题后,直接触发 onMouseOut 事件,将导航栏关闭,无法点击导航栏的子选项。因此我们要给用户一定的时间从主标题移动到子标题,当用户移动到子标题时,触发子标题上的 onMouseOver 事件,清除 setTimeout 延时并维持 isActive 状态为 true。
由于涉及到多级的状态管理,因此使用了 Redux ,但是在 Redux 中const action = {type:"...",...}只能传递对象,无法加入 setTimeout() 异步函数,因此我们又需要引入中间件Redux-thunk。thunk 中间件主要处理一些状态的异步操作问题:例如 setTimeout 延时, axios 请求等。
thunk 的基础和原理在这里不过多的介绍,可以参考:
redux-thunk最简单的讲解,7行代码用就完事了
精炼:
执行同步action:

1
2
3
4
5
6
7
8
9
function decrement(){
return { type: 'INCREMENT', payload: 1 }
}
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch({ type: 'INCREMENT', payload: 1}),
decrement: () => dispatch( decrement() )
}
}

执行异步action:

1
2
3
4
5
const delayAction = () => (dispatch, getState) => {
let timer = setTimeout(() => { dispatch({ type: "close_isActive_delay" }) }, 300)
dispatch({ type: "add_timer", timer })
}
store.dispatch(delayAction())

注意事项
dispatch内必须是一个扁平化的object,或者是能直接返回一个{type:’REDUCERS’,payload:data}的函数


在什么场景下需要用到redux的middleware,thunk?
精炼:
thunk 函数接收两个参数,其都是 Redux 的方法名

1
(dispatch, getState) => {}

thunk 可以通过 getState 方法获取 store 仓库中的所有状态,并可以在执行完自定义函数操作后将结果 dispatch 到 store 仓库中。


理解redux-thunk
技术胖-Redux-thunk
thunk 的使用方法:

  1. 安装 redux-thunk
    1
    npm install --save redux-thunk
  2. 配置 redux-thunk
    1
    2
    3
    4
    5
    6
    //在redux中使用中间件,首先要引入applyMiddleware
    import { createStore , applyMiddleware } from 'redux'
    //引入redux-thunk库
    import thunk from 'redux-thunk'
    //创建数据存储仓库
    const store = createStore( reducer, applyMiddleware(thunk))
  3. 使用 thunk
    1
    2
    3
    4
    5
    const func1 = () => (dispatch, getState) => {
    let timer = setTimeout(() => { dispatch({ type: "close_isActive_delay" }) }, 300)
    dispatch({ type: "add_timer", timer })
    }
    store.dispatch(func1())
    这里创建一个func1的函数,其返回一个 thunk 函数,thunk 函数接收两个参数,并在内部写业务逻辑,得到的 action 对象通过 dispatch 注入,并最后将 thunk 函数通过 dispatch 注入到 store 中。这里有一个点需要注意:即为什么要在 thunk 函数外嵌套一层函数,而不是直接将 thunk 函数注入到 store 中?这是因为外层嵌套函数后可以传入一些自定义的参数,由于 thunk 函数接收的两个参数是固定的,因此就有必要嵌套一层函数使得 thunk 函数可以使用用户自定义的一些业务逻辑参数,例如const func1 = (myname) => (dispatch, getState) => {...}

注意store.dispatch(func1())中注入的是函数的调用而非函数名。
当然上述函数定义也可以写为:

1
2
3
4
5
6
7
8
9
const func1 = (xxx) => {
return (
(dispatch, getState) => {
let timer = setTimeout(() => { dispatch({ type: "close_isActive_delay" }) }, 300)
dispatch({ type: "add_timer", timer })
}
)
}
store.dispatch(func1())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React, { Component } from 'react';
import { createPortal } from 'react-dom'
import "../style/DropdownMenu.css"
import { CSSTransition } from 'react-transition-group'
import store from '../store/index'

class DropdownMenu extends Component {
constructor(props) {
super(props);
this.state = {}
const dom = window.document
this.node = dom.createElement("div")
dom.body.appendChild(this.node)
}

componentWillUnmount() {
window.document.body.removeChild(this.node)
}

render() {
return createPortal((
<div
className="menuContainer"
>
<CSSTransition
in={this.props.isActive}
timeout={200}
classNames={{
enter: "menuEnter",
enterActive: "menuEnterActive",
enterDone: "menuEnterDone",
exit: "menuExit",
exitActive: "menuExitActive",
exitDone: "menuExitDone",
}}
>
<div className="dropDownMenu"></div>
</CSSTransition>
</div>
), this.node);
}
}

export default DropdownMenu;

重难点分析

主要实践了 React-Portal,其实这里可以用脱离文档流加定位的方式实现,只是单纯的为练习而使用,React-Portal 具体使用可以参考先前的文章。

页面滚动动画

PageOneText.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import React, { Component } from 'react'
import "../../style/page1/PageOne.css"
import { Typography } from 'antd';
import store from '../../store'; //可省略index.js
import "../../style/page1/PageOne.css"
import testImg from '../../images/testImg.jpg'

const { Title } = Typography;
const { Paragraph } = Typography;

class PageOneText extends Component {
constructor(props) {
super(props);
this.state = store.getState()
this.storeChange = this.storeChange.bind(this)
store.subscribe(this.storeChange)
}

handleScroll() {
if (document.documentElement.scrollTop > 900) {
const action = {
type: "handle_scroll",
}
store.dispatch(action)
}
if (document.documentElement.scrollTop < 550) {
const action = {
type: "handle_noscroll",
}
store.dispatch(action)
}
// console.log(document.documentElement.scrollTop)
}

storeChange() {
this.setState(store.getState())
}

componentDidMount() {
// 挂载 onscroll 监听事件,object.onscroll = function()
window.onscroll = () => { this.handleScroll() }
}

render() {
return (
<div className="textContainer">
<img className={`${this.state.pgoneClassNames.img}`} src={testImg} alt="" />
<Title className={`title ${this.state.pgoneClassNames.title}`}>{this.props.title}</Title>
<Paragraph
ellipsis={{ rows: 5, expandable: true, symbol: 'more' }}
className={`texts paragraph ${this.state.pgoneClassNames.text}`}
>
{this.props.text}
</Paragraph>
</div>
);
}
}

export default PageOneText;


/*
页面滚动动画机制:
页面滚动时界面会不断渲染,因此在componentDidMount生命周期中可以监听scrollTop页面位置,作为动画入场条件。
*/

重难点分析

页面滚动事件

  1. 处理页面滚动的函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    handleScroll() {
    if (document.documentElement.scrollTop > 900) {
    const action = {
    type: "handle_scroll",
    }
    store.dispatch(action)
    }
    if (document.documentElement.scrollTop < 550) {
    const action = {
    type: "handle_noscroll",
    }
    store.dispatch(action)
    }
    // console.log(document.documentElement.scrollTop)
    }
    document.documentElement返回document文档的根节点元素,例如HTML文档则返回<html>元素节点。
    通过document.documentElement.scrollTop可以获得HTML文档滚动条距离其顶部的高度,由于显示分辨率的不同,scrollTop也不同,具体调试滚动阈值的时候可以通过console.log(document.documentElement.scrollTop)来实时打印确定。

题外话:document.documentElement和document.body区别
body是DOM对象里的body子节点,即body标签
documentElement 是整个节点树的根节点root

  1. 挂载监听事件
    1
    2
    3
    4
    componentDidMount() {
    // 挂载 onscroll 监听事件,object.onscroll = function()
    window.onscroll = () => { this.handleScroll() }
    }
    上述handleScroll()只定义了监听事件的处理函数,我们还需要在合适的位置将监听函数挂载。通常用window.onScroll为当前页面的页面滚动事件添加事件处理函数。
    我们将页面滚动监听放在componentDidMount中,在页面组件渲染完成后挂载监听事件,当用户滚动页面时会不断触发监听事件所添加的事件处理函数handleScroll,当滚动距离超过阈值时,执行动画操作。

PageOne.js

用于包裹<PageOneText />组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import React, { Component } from 'react';
import "../../style/page1/PageOne.css"
import PageOneText from './PageOneText'
import testImg from '../../images/testImg.jpg'
import pgoneBackground from '../../images/pgoneBackground.png'

class PageOne extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
const itemList = [
{
title: "h1. Title1",
text: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
XXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxx\
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
imgSrc: testImg,
},
{
title: "h2. Title2",
text: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
XXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxx\
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
imgSrc: testImg,
},
{
title: "h3. Title3",
text: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
XXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxx\
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
imgSrc: testImg,
},
]
return (
<div className="pgOnePlaceholder">
<img
src={pgoneBackground}
style={{
opacity: 1,
width: "100%",
height: "100%",
position: "absolute", //脱离文档流,但是基于父级position:"relative"定位,置于背景的一种方法
}}
alt="" />
{
itemList.map((item, index) => (
<PageOneText
key={index}
imgSrc={item.imgSrc}
title={item.title}
text={item.text} />
))
}
</div>
);
}
}

export default PageOne;

CSS 动画

关于 CSS 动画,这里单独拿出一块来讲,因为这块踩的坑比较多。
我在使用 CSS 动画时,前期采用了 React 官方的一个动画库 react-transition-group ,但是由于教程比较少,一些问题得不到解决,所以后面改用了 CSS 的 keyframe 帧动画。

CSSTransition

react-transition-group CSSTransition
使用方法:

  1. 安装 react-transition-group
    1
    npm install --save react-transition-group
  2. 配置 react-transition-group
    1
    import { CSSTransition } from 'react-transition-group'
  3. 使用 CSSTransition
    <CSSTransition></ CSSTransition>标签包裹需要动画的组件(只能是一个整体,即多个子组件需要用<div>包裹)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <CSSTransition
    in={}
    classNames={}
    timeout={200}
    onEnter={(el)=>(...)}
    ...
    >
    <subComponent />
    </ CSSTransition>
    <CSSTransition> 标签参数参考官方文档,必要的参数为in, classNames, timeout,in 参数接收 true/false,表示动画执行的条件;classNames 参数设定动画的CSS样式;timeout 设定动画执行时间。
    classNames 要区别于 className,其次,classNames 有两种声明方式:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    classNames = {{
    enter: "xx",
    enterActive: "xxx",
    enterDone: "xxxx",
    }}
    ##########
    .xx {}
    .xxx {}
    .xxxx {}
    或者
    1
    2
    3
    4
    5
    classNames = "xx"
    ##########
    .xx-enter {}
    .xx-enter-active {}
    .xx-enter-done {}

使用CSSTransition的一些坑

无法设置目标的初始状态,例如一些淡入淡出的效果,目标未进行动画的时候理论上应该隐藏在用户视野之外。但在使用CSSTransition动画时,若设置目标 opacity:0,则无法播放动画效果。
可能原因:
目标还未挂载渲染时候,<CSSTransition>已经挂载,后续的CSS样式遮盖了动画效果。目前没有什么比较好的解决方法。此外,目标组件内容若是接收传递的props,也会出现上述问题。因此,在PageOne 页面制作的时候,采用了 keyframe 帧动画。

CSS Keyframes

参考链接:
CSS动画

定义关键帧

@keyframes 关键帧名称 {}
其中 0%, 100% 代表关键帧位置;opacity 表示透明度,transform 表示相对于目标初始位置的位移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@keyframes leftToRight {
0%{
opacity: 0;
transform:translateX(-100%);
}
100%{
opacity: 1;
transform:translateX(0);
}
}

@keyframes topToBottom {
0%{
opacity: 0;
transform:translateY(-100%);
}
100%{
opacity: 1;
transform:translateY(0);
}
}

定义animation动画

CSS3 animation
animation: name duration timing-function delay iteration-count direction fill-mode;包含以下几个参数:

  1. name: keyframes 的动画样式名称
  2. duration: 动画时长
  3. timing-function: ease-in 快进慢出;ease-out 慢进快出;ease-in-out;…
  4. delay:动画延迟,等待n秒后开始动画 animation-delay
  5. iteration-count:循环播放次数
  6. direction:方向
  7. fill-mode:规定动画在播放之前或之后,其动画效果是否可见。 none | forwards | backwards | both; CSS3 animation-fill-mode

其中 animation-fill-mode 是解决动画目标初始化的关键,当 animation-fill-mode 设定为 both 时,以上述代码为例,动画播放前,目标元素动画效果可见,其为opacity:0; transform:translateX(-100%),即隐藏,而动画播放结束后,目标元素动画效果将会停留在关键帧100%的地方,即显示。

1
2
3
4
5
6
7
8
9
10
11
.item_row {
animation: leftToRight 1000ms both ease-out;
}

.item_delay_row {
animation: leftToRight 1000ms ease-out 100ms both;
}

.item_column {
animation: topToBottom 1500ms both ease-out;
}

定义触发动画的监听事件

监听事件由上述的页面滚动触发。首先我们需要设定一个充当占位的样式(可为空),此处visibility设为hidden表示该元素不可见但仍保留其布局空间。当监听事件触发时,我们向className中添加相应动画的CSS样式,替换原有的占位样式即可。

1
2
3
.hidden {
visibility: hidden;
}
1
2
3
4
5
6
7
8
<img className={`${this.state.pgoneClassNames.img}`} src={testImg} alt="" />
<Title className={`title ${this.state.pgoneClassNames.title}`}>{this.props.title}</Title>
<Paragraph
ellipsis={{ rows: 5, expandable: true, symbol: 'more' }}
className={`texts paragraph ${this.state.pgoneClassNames.text}`}
>
{this.props.text}
</Paragraph>

采用 CSS Keyframes 的另一个好处是能够将动画 CSS 样式和初始 CSS 样式分开,例如下述代码title ${xxx},用 ES6 模板字符串的方式,title 是固定的初始样式,${xxx}内可以传入动态的动画样式。

CSS 样式布局心得

调整CSS样式心得:

  1. 从网页打开F12,选择元素查看其className进行定位。
  2. width,height 宽高100%(或百分比设置)针对的是上一级元素(父级元素),
    因此若多余空白的
    包裹应将其删除,或者将上级空白div设置宽高100%。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!