前端逐日实战:162# 视频演示如何用原生 JS 创作一个查询 github 用户的运用(内含 2 个视频)

《前端逐日实战:162# 视频演示如何用原生 JS 创作一个查询 github 用户的运用(内含 2 个视频)》

结果预览

按下右边的“点击预览”按钮能够在当前页面预览,点击链接能够全屏预览。

https://codepen.io/comehope/pen/oQGqaG

可交互视频

此视频是能够交互的,你能够随时停息视频,编辑视频中的代码。

请用 chrome, safari, edge 翻开寓目。

第 1 部份:
https://scrimba.com/p/pEgDAM/cEPkVUg

第 2 部份:
https://scrimba.com/p/pEgDAM/crp63TR

(由于 scrimba 不支持 web animation api,第 2 部份末端的动画结果在视频中看不到,请参考 codepen)

源代码下载

逐日前端实战系列的悉数源代码请从 github 下载:

https://github.com/comehope/front-end-daily-challenges

代码解读

这是一个读取 github 用户的运用,在搜刮框内输入用户名,点击搜刮后,就会经由历程 github 的 open api 获得用户信息,填写到下方的卡片中。

全部运用分红 3 个步骤开辟:静态的页面规划、从 open api 读取数据并绑定到页面中、增添动画结果。

一、页面规划

定义 dom,团体构造分红上部的表单和下部的用户卡片:

<div class="app">
    <form>
        <!-- 暂略 -->
    </form>
    <div class="profile">
        <!-- 暂略 -->
    </div>
</div>

居中显现:

body {
    margin: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #383838;
}

定义运用容器的尺寸:

.app {
    width: 320px;
    height: 630px;
    font-family: sans-serif;
    position: relative;
}

这是表单的 dom 构造,2 个表单控件离别是文本输入框 #username 和搜刮按钮 #search,由于背面的剧本要援用这 2 个控件,所认为它们定义了 id 属性,接下来在 css 中也运用 id 选择器:

<form>
    <input placeholder="Who are you looking for?" id="username">
    <input type="button" value="Search" id="search">
</form>

令 2 个表单控件横向分列:

form {
    height: 50px;
    background-color: rgba(255, 255, 255, 0.2);
    border-radius: 2px;
    box-sizing: border-box;
    padding: 8px;
    display: flex;
}

离别设置 2 个表单控件的款式:

input {
    border: none;
    font-size: 14px;
    outline: none;
    border-radius: inherit;
    padding: 0 8px;
}
  
#username {
    flex-grow: 1;
    background-color: rgba(255, 255, 255, 0.9);
    color: #42454e;
}
  
#search {
    background-color: rgba(0, 97, 145, 0.75);
    color: rgba(255, 255, 255, 0.8);
    font-weight: bold;
    margin-left: 8px;
    cursor: pointer;
}

为按钮增添悬停和点击的交互结果:

#search:hover {
    background-color: rgba(0, 97, 145, 0.45);
}

#search:active {
    transform: scale(0.98);
    background-color: rgba(0, 97, 145, 0.75);
}

至此,表单规划完成,接下来做用户卡片规划。
用户卡片的 dom 构造以下,卡片分红上半部份 .header 和下半部份 .footer,上半部份包括头像 .avatar、名字 .name 和位置 .location,下半部份包括一组细致的数据 .details 和一个跳到 github 的链接 .to-github

<div class="profile">
    <div class="header">
        <div class="avatar"></div>
        <h2 class="name">Octocat</h2>
        <h3 class="location">San Francisco</h3>
    </div>
    <div class="footer">
        <ul class="details">
            <li>Repositories<span>111</span></li>
            <li>Followers<br><span>222</span></li>
            <li>Following<br><span>333</span></li>
        </ul>
        <a href="#" class="to-github">go to github</a>
    </div>
</div>

令卡片的上半部份和下半部份竖向分列,并离别设置两部份的高度,大约是上半部份占卡片高度的三分之二,下半部份占卡片高度的三分之一,此时能够看出卡片的表面了:

.profile {
    width: 320px;
    position: absolute;
    margin: 20px 0 0 0;
    display: flex;
    flex-direction: column;
    border-radius: 5px;
}

.header {
    height: 380px;
    background-color: rgba(0, 97, 145, 0.45);
}

.footer {
    height: 180px;
    background-color: rgba(0, 97, 145, 0.75);
}

令卡片上半部份的子元素竖向分列:

.header {
    display: flex;
    flex-direction: column;
    align-items: center;
}

设置头像图片,款式为描边的圆形,由于头像图片在背面还会用到,所以把它存储到变量 --avatar 中:

.profile {
    --avatar: url('https://avatars3.githubusercontent.com/u/583231?v=4');
}

.avatar {
    width: 140px;
    height: 140px;
    background-image: var(--avatar);
    margin: 70px 0 0 0;
    background-position: center;
    background-size: cover;
    border-radius: 50%;
    box-shadow: 
        0 0 0 0.8em rgba(0, 0, 0, 0.2),
        0 0 0 1em rgba(161, 220, 255, 0.35);
}

设置名字和位置信息的款式,笔墨为白色:

.name {
    margin: 50px 0 0 0;
    color: white;
    font-size: 28px;
    font-weight: normal;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

.location {
    margin: 5px 0 0 0;
    color: rgba(255, 255, 255, 0.75);
    font-weight: normal;
}

至此,上半部份的规划完成,接下来规划下半部份。
令下半部份的子元素竖向分列:

.footer {
    display: flex;
    flex-direction: column;
    align-items: center;
}

横向分列三组数据,每项之间到场细分隔线:

.details {
    list-style-type: none;
    padding: 0;
    display: flex;
    margin: 40px 0 0 0;
}

.details li {
    color: rgba(255, 255, 255, 0.6);
    text-align: center;
    padding: 0 6px;
}

.details li span {
    display: block;
    color: rgba(255, 255, 255, 0.8);
}

.details li:not(:first-child) {
    border-left: 2px solid rgba(255, 255, 255, 0.15);
}

设置跳转到 github 的链接款式和悬停结果:

.to-github {
    width: 200px;
    height: 40px;
    background-color: rgba(255, 255, 255, 0.5);
    text-align: center;
    line-height: 40px;
    color: rgba(0, 0, 0, 0.75);
    text-decoration: none;
    text-transform: uppercase;
    border-radius: 20px;
    transition: 0.3s;
}

.to-github:hover {
    background-color: rgba(255, 255, 255, 0.8);
}

至此,下半部份规划完成。
接下来用伪元素把头像图片作为团体背景:

.profile {
    position: relative;
    overflow: hidden;
}

.profile::before {
    content: '';
    position: absolute;
    width: calc(100% + 20px * 2);
    height: calc(100% + 20px * 2);
    background-image: var(--avatar);
    background-size: cover;
    z-index: -1;
    margin: -20px;
    filter: blur(10px);
}

到这里,团体的静态规划就完成了。

二、绑定数据

为了绑定数据,我们引入一个羽量级的模板库:

<script src="https://blueimp.github.io/JavaScript-Templates/js/tmpl.min.js"></script>

把卡片 .profile 包括的 dom 构造改写为 html 模板 #template,个中的 o 代表绑定的数据数据对象:

<script type="text/x-tmpl" id="template">
    <div class="header">
        <div class="avatar"></div>
        <h2 class="name">{%= o.name %}</h2>
        <h3 class="location">{%= o.location %}</h3>
    </div>
    <div class="footer">
        <ul class="details">
            <li>Repositories<span>{%= o.public_repos %}</span></li>
            <li>Followers<br><span>{%= o.followers %}</span></li>
            <li>Following<br><span>{%= o.following %}</span></li>
        </ul>
        <a href="{%= o.html_url %}" class="to-github">go to github</a>
    </div>
</script>

声明一个假数据对象 mockData,它的数据构造与 github open api 的数据构造是一致的:

let mockData = {
    "avatar_url": "https://avatars3.githubusercontent.com/u/583231?v=4",
    "name": "The Octocat",
    "location": "San Francisco",
    "public_repos": 111,
    "followers": 222,
    "following": 333,
    "html_url": "https://github.com/octocat",
}

定义一个把数据绑定到 html 模板的函数 render(container, data),第 1 个参数 container 示意 dom 容器,模板内容将添补在此容器中;第 2 个参数是数据对象。在页面载入时挪用 render() 要领,把 mockData 作为参数传入,此时看到的结果和纯静态的结果一致,但用户卡片已改成动态建立了:

window.onload = render(document.getElementsByClassName('profile')[0], mockData)

function render(container, data) {
    container.innerHTML = tmpl('template', data)
    container.style.setProperty('--avatar', `url(${data.avatar_url})`)
}

定义一个从 github open api 读取用户信息的要领 getData(username),然后挪用 render() 要领把用户信息绑定到 html 模板。同时,把 window.onload 绑定的事宜改成挪用 getData() 要领,此时看到的结果仍和纯静态的结果一致,但数据已变成动态读取了:

window.onload = getData('octocat')

function getData(username) {
    let apiUrl = `https://api.github.com/users/${username}`
    fetch(apiUrl)
        .then((response) => response.json())
        .then((data) => render(document.getElementsByClassName('profile')[0], data))
}

为表单的 search 按钮绑定点击事宜,完成搜刮功用。能够查一下本身的 github 帐号碰运气:

document.getElementById('search').addEventListener('click', () => {
    let username = document.getElementById('username').value.replace(/[ ]/g, '')
    if (username == '') {
        return
    }
    getData(username)
})

三、增添动画结果

为了能让用户感受到每次搜刮后数据的变化历程,我们增添一点动画结果。建立一个 update(data) 函数来处置惩罚动画和衬着逻辑,同时把 getData() 函数的末了一步改成挪用 update() 函数:

function getData(username) {
    let apiUrl = `https://api.github.com/users/${username}`
    fetch(apiUrl)
        .then((response) => response.json())
        // .then((data) => render(document.getElementsByClassName('profile')[0], data))
        .then(update)
}

function update(data) {
    let current = document.getElementsByClassName('profile')[0]
    render(current, data)
}

当页面初次载入时,不需要动画,直接衬着默许的用户信息即可。变量 isInitial 示意本次挪用是不是是在初始化页面时挪用的,如果,就直接衬着。若不是,下面会实行动画结果。

function update(data) {
    let current = document.getElementsByClassName('profile')[0]
    let isInitial = (current.innerHTML == '')

    if (isInitial) {
        render(current, data)
        return
    }
}

动画的历程是:建立一张新卡片,把数据绑定到新卡片上,然后把当前卡片移出视图,再把新卡片移入视图。下面的变量 next 代表新建立的卡片,把它定位到当前卡片的右边:

function update(data) {
    let current = document.getElementsByClassName('profile')[0]
    let isInitial = (current.innerHTML == '')

    if (isInitial) {
        render(current, data)
        return
    }

    let next = document.createElement('div')
    next.className = 'profile'
    next.style.left = '100%'
    render(next, data)
    current.after(next)
}

由于动画分红 2 个行动——当前卡片移出和新卡片移入,所以我们定义 2 个动画结果,变量 animationOut 代表移出动画的参数,变量 animationIn 代表移入动画的参数。个中,keyframes 属性值相当于写 css 动画时用 @keyframes 定义的关键帧,options 属性值相当于写 css 动画时 animation 语句背面的参数,新卡片移入动画有半秒钟的延时。

function update(data) {
    let current = document.getElementsByClassName('profile')[0]
    let isInitial = (current.innerHTML == '')

    if (isInitial) {
        render(current, data)
        return
    }

    let next = document.createElement('div')
    next.className = 'profile'
    next.style.left = '100%'
    render(next, data)
    current.after(next)

    let animationOut = {
        keyframes: [
            {left: '0', opacity: 1, offset: 0},
            {left: '-100%', opacity: 0, offset: 1}
        ],
        options: {
            duration: 500,
            fill: 'forwards'
        }
    }

    let animationIn = {
        keyframes: [
            {left: '100%', opacity: 0, offset: 0},
            {left: '0', opacity: 1, offset: 1}
        ],
        options: {
            duration: 500,
            fill: 'forwards',
            delay: 500
        }
    }
}

由于动画需异步实行,即在当前卡片移出的动画完毕后再实行新卡片移入的动画,所以我们令当前卡片移出的动画完毕后触发 onfinish 事宜,然后再实行新卡片移入的动画,同时把旧卡片删撤除:

function update(data) {
    let current = document.getElementsByClassName('profile')[0]
    let isInitial = (current.innerHTML == '')

    if (isInitial) {
        render(current, data)
        return
    }

    let next = document.createElement('div')
    next.className = 'profile'
    next.style.left = '100%'
    render(next, data)
    current.after(next)

    let animationOut = {
        keyframes: [
            {left: '0', opacity: 1, offset: 0},
            {left: '-100%', opacity: 0, offset: 1}
        ],
        options: {
            duration: 500,
            fill: 'forwards'
        }
    }

    let animationIn = {
        keyframes: [
            {left: '100%', opacity: 0, offset: 0},
            {left: '0', opacity: 1, offset: 1}
        ],
        options: {
            duration: 500,
            fill: 'forwards',
            delay: 500
        }
    }

    let animate = current.animate(animationOut.keyframes, animationOut.options)
    animate.onfinish = function() {
        current.remove()
        next.animate(animationIn.keyframes, animationIn.options)
    }
}

末了,限制动画结果仅在 .app 容器中展示:

.app {
    overflow: hidden;
}

功德圆满!

    原文作者:comehope
    原文地址: https://segmentfault.com/a/1190000017064196
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞