在构建为用户提供一长串信息的应用程序时,例如新闻提要、论坛中的帖子或聊天应用程序中的消息,我们的目标是向用户显示合理数量的信息。

用户开始滚动浏览初始列表后,Web 客户端应立即加载更多内容以继续向用户提供此信息。 这个过程被称为无限滚动。

想象一下,您正在浏览地球上每个人的姓名列表。 尽管该列表并非完全无限,但确实感觉如此。 您的浏览器可能难以处理从 GraphQL 服务器抛出的超过 80 亿个项目的列表。

这就产生了分页的需要。 GraphQL API 中的分页允许客户端或我们的前端从 API 中查询部分项目列表。 在我们的前端,我们现在可以构建无限滚动功能。

本文将讨论 GraphQL 中的分页概念及其工作原理。 我们将深入探讨无限滚动的概念及其应用、优缺点。 我们还将了解如何将 Vue 前端连接到提供数据列表的演示 GraphQL API。 有了它,我们将演示如何在前端实现无限滚动以获取数据。

  • 分页和无限滚动

    • 编号分页

    • 加载更多分页

    • 无限滚动

  • GraphQL 中的分页

    • 基于偏移的分页

    • 基于光标的分页

    • 基于 ID 的分页

  • 无限滚动的工作原理

    • 滚动事件处理程序

    • 交叉口观察者 API

  • 构建我们的应用程序

    • 设置我们的演示 API

    • 创建我们的 Vue 应用程序

    • 构建无限滚动功能

    • 使用滚动事件处理程序进行无限滚动

    • 使用 Intersection Observer API 进行无限滚动

分页和无限滚动

分页是将内容分离或划分为称为页面的离散部分的过程。

虽然我们在本文中试图完成的工作并不涉及我们创建页面来显示内容,但它仍然依赖于将内容分成几部分以便在用户滚动时加载它的概念。

无限滚动是我们今天在应用程序中看到的三种主要分页形式之一 。淘游街机游戏平台App,内置5000多款的街机游戏全免费,自带模拟器运行! 让我们快速浏览一下所有三种常见的分页形式:编号、加载更多和无限滚动。

编号分页

编号分页通常是我们谈论分页时的意思。 在这种形式中,内容被划分并显示在单独的页面上。

这种分页形式的一个很好的例子是谷歌搜索结果。

加载更多分页

加载更多方法是另一种常见的分页形式。 这是“加载更多”按钮位于列表末尾的位置,单击时会加载更多项目。 它也可以以“下一步”按钮的形式出现,加载更多项目但不一定在同一页面上。


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →


加载更多分页方法的一个很好的例子是在博客上。 列表底部通常有一个按钮,单击该按钮可加载更多博客文章。 这种方法非常适合您希望用户在页面末尾看到页脚的网站。

对于未编号的页面,还有另一种形式的加载更多分页方法。 例如,您可以通过这些按钮在旧版 Reddit 中加载更多内容。

无限滚动

无限滚动在用户可以继续滚动一长串项目的提要中尤其常见。 这种方法没有页面的概念,但用户滚动浏览的列表是较短列表部分的组合。 主列表的这些子集在用户滚动时被获取和添加。

无限滚动是新版 Reddit 以及大多数社交媒体平台所使用的。

GraphQL 中的分页

我们已经看到了在应用程序中实现分页的常用方法,现在让我们看看它在 GraphQL 中是如何工作的。 喜马拉雅FM机车版App,开车必备听书神器,有声小说资源丰富种类全!虽然没有一种特定的分页方式,但 GraphQL 官方网站为我们提供了三种主要的分页方式 :

  • 基于偏移的分页

  • 基于光标的分页

  • 基于 ID 的分页

基于偏移的分页

这种方法通常被认为是 GraphQL 中最简单的分页形式。 对于基于偏移量的分页,查询通常包含两个参数: first(或者 limit) 和 offset(或者 skip)。 这 first参数定义列表返回的项目数和 offset参数定义了我们应该在列表中跳过多少。

使用这种分页设置,您的查询可能如下所示:

query {people(first: 3, offset: 0) {name}
}

此查询将从 0 开始,即第一个人,从列表中获取三个人。 我们最终得到了名单上的前三个人。

您也可以决定不从列表中的第一个人开始。 也许您希望第二页从列表中的第四人开始再显示另外三个人。 不是问题! 您的查询参数只需稍作更改:

query {people(first: 3, offset: 3) {name}
}

结果现在将偏移三个项目,并从第四个项目而不是第一个项目开始。

我们也可以自由更改 first和 offset值来满足我们的需要。 以下查询从列表中获取四个项目,其中 offset的 1:

query {people(first: 4, offset: 1) {name}
}

这意味着列表将包含从第二个开始的四个项目。


来自 LogRocket 的更多精彩文章:

  • 不要错过 The Replay 来自 LogRocket 的精选时事通讯

  • 了解 LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题

  • 使用 React 的 useEffect 优化应用程序的性能

  • 之间切换 在多个 Node 版本

  • 了解如何 使用 AnimXYZ 为您的 React 应用程序制作动画

  • 探索 Tauri ,一个用于构建二进制文件的新框架

  • 比较 NestJS 与 Express.js


这就是基于偏移的分页的基础。 尽管它很简单,但它也有其缺点——即它容易重复或省略数据。 此问题主要发生在分页期间将新项目添加到列表中时。

添加新项目时,数组中项目的位置可能会发生变化。电视电台直播App,涵盖了国内各地区直播频道,高清视频画质无限制播放! 而且因为 offset依赖于项目位置,如果一个项目被添加到列表的顶部,前一页上的最后一个列表项目可能会成为下一页上的第一个项目,因为它的位置现在较低。

基于光标的分页

基于光标的分页是 GraphQL 中最广泛接受的分页标准。 它对分页时列表中发生的更改具有弹性,因为它不依赖于项目的位置,而是依赖于 cursor.

基于光标的分页可以通过多种方式实现。 最常见且被广泛接受的一种遵循 Relay GraphQL 连接规范 。

为基于光标的分页实现 Relay 连接规范可能很复杂,但为我们提供了更多的灵活性和信息。

该规范为我们提供了一些可用于分页的参数。 他们包括 first和 after(或者 afterCursor) 用于前向分页和 last和 before用于向后分页。 我们还可以访问多个字段。

让我们评估这个查询:

query ($first: Int, $after: String) {allPeople(first: $first, after: $after){pageInfo {hasNextPageendCursor}edges {cursornode {idname}}}
}

你会注意到参数 first和 after对于这些领域,我们有:

  • pageInfo

    : 包含页面上的信息,包括:

    • hasNextPage:当前页面(或部分、子集)之后是否还有其他项目

    • endCursor: 当前页面最后一个列表项的光标

  • edges

    : 包含列表中每个项目的以下数据:

    • cursor:通常是 的不透明值 可以从项目数据中生成

    • node:实际项目数据

现在,用以下变量回顾上面的查询:

{"first": 2,"after": "YXJyYXljb25uZWN0aW9uOjI="
}

这是我们得到的回应:

{"allPeople": {"pageInfo": {"hasNextPage": true,"endCursor": "YXJyYXljb25uZWN0aW9uOjQ="},"edges": [{"cursor": "YXJyYXljb25uZWN0aW9uOjM=","node": {"id": "cGVvcGxlOjQ=","name": "Darth Vader"}},{"cursor": "YXJyYXljb25uZWN0aW9uOjQ=","node": {"id": "cGVvcGxlOjU=","name": "Leia Organa"}}]}
}

您可以 [在 SWAPI GraphiQL 操场 ](SWAPI GraphQL API query (%24first%3A Int%2C %24after%3A String) { allPeople(first%3A %24first%2C after%3A %24after)上亲自试用此查询。

基于 ID 的分页

虽然基于 ID 的分页不像其他两种方法那样常用,但它仍然值得讨论。

这种方法与基于偏移的分页非常相似。 不同的是,而不是使用 offset为了确定返回的列表应该从哪里开始,它使用 afterID(或者干脆 after) 接受 id列表中的一项。

看看这个查询:

query {people(first: 3, after: "C") {name}
}

这将从列表中的前三个项目中获取 id的 C.

这有助于解决基于偏移的分页问题,因为返回的项目不再依赖于项目的位置。 现在,他们依靠自己使用的物品 id作为唯一标识符。

好的,既然我们已经熟悉了 GraphQL 中的分页是如何工作的,那么让我们深入了解无限滚动!

无限滚动的工作原理

我想假设我们之前都至少使用过一次社交媒体或新闻源应用程序。 因此,我们应该熟悉无限滚动的全部内容。 新内容会在您到达页面底部之前或同时添加到提要中。

For JavaScript applications, we can implement infinite scrolling in two main ways: either with scroll event handlers or the Intersection Observer API.

Scroll event handlers

For scroll event handlers, we run a function that will fetch the following page on two conditions:

  • The scroll position is at the bottom of the page

  • There is a next page to fetch

A generic JavaScript code for this method would look something like this:

 window.addEventListener('scroll', () => {let {scrollTop,scrollHeight,clientHeight} = document.documentElement;
​if (scrollTop + clientHeight >= scrollHeight && hasNextPage) {fetchMore()}
});

在这里,我们正在听一个 scroll中的事件 window. 在回调函数中,我们得到 scrollTop, scrollHeight, 和 clientHeight的文件。

然后,我们有一个 if检查金额是否滚动的语句( scrollTop) 添加到视口的高度 ( clientHeight) 大于或等于页面高度 ( scrollHeight),以及如果 hasNextPage是真的。

如果语句为真,则运行函数 fetchMore()获取更多项目并将它们添加到列表中。

交叉口观察者 API

与滚动事件处理方法不同,此方法不依赖滚动事件。 相反,它会监视元素何时在视口上可见并触发事件。

这是一个基本示例:

const options = {root: document.querySelector("#list"),threshold: 0.1,
};let observer = new IntersectionObserver((entries) => {const entry = entries[0];if (entry.isIntersecting) {fetchMore()}
}, options);observer.observe(document.querySelector("#target"));

我们使用观察者的设置来定义选项。 有了它,我们正在观察 target根中的元素。 这 root这是一个元素 id的 list. 我们还有一个 threshold这决定了有多少 target元素与根元素相交。

我们分配 IntersectionObserver作用于 observer.value. 然后我们传递一个回调函数以及定义的 options.

回调接受参数 entries, 回调收到的条目列表,带有 entry对于报告其交集状态发生变化的每个目标。 每个条目都包含几个属性,例如 isIntersecting,它告诉我们目标元素现在是否与根相交,并且在大多数情况下是可见的。

一次 entry.isIntersecting是真的, fetchMore()函数被触发并将更多项目添加到列表中。

构建我们的应用程序

我们将使用 Apollo Client 构建一个简单的 Vue 应用程序,以与演示 GraphQL API 交互。 您可以 在 Netlify 上找到我们将要构建的最终项目 。

要开始,您需要:

  • 文本编辑器——例如 VSCode

  • 基础知识 Vue

  • 基础知识 GraphQL

  • 安装了最新的 Node.js 版本

设置我们的演示 API

在本教程中,我们将使用 SWAPI GraphQL Wrapper,它是使用 GraphQL 构建的围绕 SWAPI 的包装器。

首先, 获取存储库: 从 GitHub

git clone https://github.com/graphql/swapi-graphql.git

然后使用以下内容安装依赖项:

npm install

使用以下命令启动服务器:

这将在随机 localhost 端口启动 GraphQL API 服务器。

如果您在 Windows 上并在安装时遇到与 此问题中提到的 问题类似的任何问题,您可以按照说明进行解决。 在 package.json, 你也可以编辑第 40 行 ( build:lambda)添加 SET前 NODE_ENV – SET NODE_ENV. 然后运行 npm install再次。

Alternatively, you can simply use this deployed version for your queries.

Creating our Vue app

To create a new app, navigate to the directory of your choice and run:

npm init vue@latest

Now, go through the prompts to configure your installation:

√ Project name: ... vue-infinite-scroll
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add Cypress for both Unit and End-to-End testing? ... No / Yes
√ Add ESLint for code quality? ... No / Yes
​
Scaffolding project in C:\Users\user\Documents\otherprojs\writing\logrocket\vue-infinite-scroll...
​
Done. Now run:cd vue-infinite-scrollnpm installnpm run dev

导航到您新创建的 vue-infinite-scroll目录,安装上面输出中列出的包并启动应用程序:

cd vue-infinite-scroll
npm install
npm run dev

接下来,我们将安装以下软件包:

npm install --save graphql graphql-tag @apollo/client @vue/apollo-composable

我们正在安装额外的 @vue/apollo-composable使用 Vue Composition API 支持 Apollo 的包。

接下来,让我们进行一些配置。

在里面 ./src/main.js文件,添加以下内容以创建一个 ApolloClient实例:

// ./src/main.js
​
import { createApp, provide, h } from 'vue'
import { createPinia } from 'pinia'
​
import { ApolloClient, InMemoryCache } from '@apollo/client/core'
import { DefaultApolloClient } from '@vue/apollo-composable'
​
import App from './App.vue'
import './assets/base.css'
​
// Cache implementation
const cache = new InMemoryCache()
​
// Create the apollo client
const apolloClient = new ApolloClient({cache,uri: 'http://localhost:64432'// or// uri: 'https://swapi-gql.netlify.app/.netlify/functions/index`
})
​
const app = createApp({setup() {provide(DefaultApolloClient, apolloClient)},render: () => h(App)
})
​
app.use(createPinia())
app.mount('#app')

在这里,我们创建了一个 apolloClient实例与 InMemoryCache和 uri作为我们之前设置的 SWAPI GraphQL 服务器。

现在,我们将从 GraphQL 获取数据。 在里面 ./src/App.vue文件,让我们设置我们的列表:

<!-- ./src/App.vue -->
<script setup>
import { computed, onMounted, ref } from "vue";
​
import gql from "graphql-tag";
import { useQuery } from "@vue/apollo-composable";
​
// GraphQL query
const ALLSTARSHIPS_QUERY = gql`query AllStarships($first: Int, $after: String) {allStarships(first: $first, after: $after) {pageInfo {hasNextPageendCursor}edges {cursornode {idnamestarshipClass}}}}
`;
​
// destructure
const {// result of the queryresult,// loading state of the queryloading,// query errors, if anyerror,// method to fetch morefetchMore,// access to query variablesvariables
} = useQuery(ALLSTARSHIPS_QUERY, { first: 5 });
​
// computed value to know if there are more pages after the last result
const hasNextPage = computed(() => result.value.allStarships.pageInfo.hasNextPage);
​
</script>
<template><main><ul class="starship-list"><p v-if="error">oops</p><!-- "infinite" list --><li v-else v-for="starship in result?.allStarships.edges" :key="starship.node.id" class="starship-item"><p>{{ starship.node.name }}</p></li></ul><!-- target button, load more manually when clicked --><button ref="target" @click="loadMore" class="cta"><span v-if="loading">Loading...</span><span v-else-if="!hasNextPage">That's a wrap!</span><span v-else>More</span></button></main>
</template>
<style scoped>
button {cursor: pointer;
}
main {width: 100%;max-width: 30rem;margin: auto;padding: 2rem;
}
.starship-list {list-style: none;padding: 4rem 0 4rem 0;
}
.starship-item {font-size: xx-large;padding: 1rem 0;
}
.cta {padding: 0.5rem 1rem;background: var(--vt-c-white-soft);color: var(--color-background-soft);border: none;border-radius: 0.5rem;
}
</style>

我们首先导入 gql来自 graphql-tag包装和 useQuery从 @vue/apollo-composable. useQuery允许我们进行 GraphQL 查询。

接下来,我们设置查询 ALLSTARSHIPS_QUERY与 first和 after我们将在进行查询时定义的变量。

为了进行查询,我们使用 useQuery(). useQuery()提供了几个属性,如 result, loading, error, fetchMore, 和 variables.

打电话时 useQuery(),我们传入实际的查询 ALLSTARSHIPS_QUERY和一个包含我们变量的对象, { first: 5 }, 以获取前五个项目。

另外,我们有一个 <button>和 ref="target"在我们的 <template>. 这是我们的“加载更多”按钮。

随着我们的进展,我们只会使用它来观察我们何时到达列表的末尾,并使用 Intersection Observer API 自动加载更多内容。

这是我们现在应该拥有的:

构建无限滚动功能

让我们一步一步来看看我们如何使用目标按钮在单击时加载更多项目。 这对阿波罗来说很容易。 阿波罗提供 fetchMore()顾名思义,我们可以使用该方法获取更多内容并将其与原始结果合并。

为此,我们将包装 fetchMore()在一个 loadMore()作用于 ./src/App.vue:

<!-- ./src/App.vue -->
<script setup>
// ...
​
// function to load more content and update query result
const loadMore = () => {// fetchMore function from `useQuery` to fetch more content with `updateQuery`fetchMore({
​// update `after` variable with `endCursor` from previous resultvariables: {after: result.value?.allStarships.pageInfo.endCursor,},
​// pass previous query result and the new results to `updateQuery`updateQuery: (previousQueryResult, { fetchMoreResult }) => {// define edges and pageInfo from new resultsconst newEdges = fetchMoreResult.allStarships.edges;const pageInfo = fetchMoreResult.allStarships.pageInfo;
​// if newEdges actually have items,return newEdges.length? // return a reconstruction of the query result with updated values{// spread the value of the previous result...previousQueryResult,
​allStarships: {// spread the value of the previous `allStarhips` data into this object...previousQueryResult.allStarships,
​// concatenate edgesedges: [...previousQueryResult.allStarships.edges, ...newEdges],
​// override with new pageInfopageInfo,},}: // else, return the previous resultpreviousQueryResult;},});
};

Here, we have the loadMore() function that calls the fetchMore() method. fetchMore() accepts an object of variables and updateQuery().

We will define the updated variables in the variables property. Here, we update the after variable to correspond with the last cursor from the first (or previous) result.

在 updateQuery(), however, we get and define edges and pageInfo from new query results and reconstruct the query result, if any. We retain the values of the previous result object by using the spread syntax to concatenate object properties or entirely replace them with the new one (like with pageInfo, for example ).

至于边缘,我们将新结果添加到 edges大批。 还记得“目标”按钮吗? 我们有一个 @click调用 loadMore()功能:

<button ref="target" @click="loadMore" class="cta">

现在,我们应该让我们的应用程序在按下按钮时加载更多的星舰:

惊人的! 让我们把推进器调高一点,看看我们如何摆脱手动操作以获得真正的无限滚动感觉。 首先,我们将看看如何使用滚动事件处理程序来做到这一点。

使用滚动事件处理程序进行无限滚动

这将与我们之前解释的类似。 在 ./src/App.vue,我们有一个 onMounted()挂载应用程序后,我们将开始监听滚动事件的钩子:

<!-- ./src/App.vue -->
<script setup>
// ...
​
onMounted(() => {// listen to the scroll event in the window object (the page)window.addEventListener("scroll",() => {// definelet {// the amount useer has scrolledscrollTop,// the height of the pagescrollHeight,// the height of viewportclientHeight} = document.documentElement;// if user has scrolled to the bottom of the pageif (scrollTop + clientHeight >= scrollHeight && hasNextPage.value) {// exccute the loadMore function to fetch more itemsloadMore();}},{// indicate that the listener will not cancel the scrollpassive: true,});
});
</script>

You can see that in the scroll event listener callback, we execute the loadMore() function when the user scrolls to the bottom of the page.

A drawback to the scroll event handler method is that the user has to scroll for this to work. Ensure that the content on the page is not too small for the user to scroll through.

Let’s see it in action:

甜的! 继续,让我们看看如何使用 Intersection Observer API 实现相同的目标。

使用 Intersection Observer API 进行无限滚动

对于这种方法,我们需要一个观察元素,它会告诉我们何时到达列表的末尾。 对于我们来说,没有比我们的按钮更好的元素了!

以我们的按钮为目标 <script>,我们将创建一个 target(在使用相同的名称 ref中按钮的属性 <template>) 变量并将其分配给 ref(null).

我们还将创建一个 ref为了 observer这将是我们的 IntersectionObserver()在里面 onMounted()钩:

<!-- ./src/App.vue -->
<script setup>
// ...
​
// create ref for observer
const observer = ref(null);
​
// create ref for target element for observer to observe
const target = ref(null);
​
onMounted(() => {// ...
​// options for observerconst options = {threshold: 1.0,};
​// define observerobserver.value = new IntersectionObserver(([entry]) => {// if the target is visibleif (entry && entry.isIntersecting) {// load more contentloadMore();}}, options);
​// define the target to observeobserver.value.observe(target.value);
});
</script>

在这里,在我们的 onMounted()钩子,我们首先定义我们将传递给我们的选项 observer.

我们通过初始化一个新的来定义观察者 IntersectionObserver()并与我们一起传递一个回调函数 options.

该函数采用解构 [entry]作为参数。 这 if语句然后确定条目是否与根相交。 这个交叉点意味着目标在视口上可见并执行 loadMore()功能。

这 entry由我们传递给的参数决定 observer.observe().

这样,我们就有了一个非常简洁的无限滚动列表!

结论

我们终于得到它了! 我们创建了一个简单的 Vue 应用程序,它从具有无限滚动功能的 GraphQL API 获取数据。

我们介绍了分页的基础知识、它的不同形式以及分页在 GraphQL 中的工作原理。 我们讨论了无限滚动以及如何在 JavaScript 中实现效果。 我们还深入研究了实现它的两种主要方法:滚动事件处理程序和 Intersection Observer API。

使用 Vue 和 Apollo Client 构建应用程序,将其连接到 GraphQL API,并使用滚动事件处理程序和 Intersection Observer API 构建无限滚动功能,我们玩得很开心。

无限滚动是我们可以构建大量向用户显示的信息的众多方式之一。 它有其优点和缺点,但是通过我们在这里介绍的所有内容,我相信您将能够确定它是否是您下一个大型项目的最佳方法!

使用 GraphQL 无限滚动相关推荐

  1. 使用RecycleView实现无限滚动的日历

    最终效果 项目地址 一.无限滚动实现 在RecyclerView.Adapter的getItemCount()方法中返回Integer.MAX_VALUE,使用RecycleView的scrollTo ...

  2. vue2.0无限滚动加载数据插件

      做vue项目用到下拉滚动加载数据功能,由于选的UI库(element)没有这个组件,就用Vue-infinite-loading 这个插件代替,使用中遇到的一些问题及使用方法,总结作记录! 安装: ...

  3. 15个非常棒的jQuery无限滚动插件【瀑布流效果】

    现在,最热门的网站分页趋势之一是jQuery的无限滚动(也即瀑布流).如果你碰巧观察Pinterest的网站,如Facebook,Twitter和deviantART的,你会发现无限滚动的动作,当旧的 ...

  4. 响应式的无限滚动全屏dribbble作品集布局展示效果

    来源:GBin1.com 在线演示/下载 大家还记得前面分享过的两个魔术布局效果吧: 响应式的dribbble作品集魔术布局展示效果 宽度自适应缩进的响应式dribbble作品集魔术布局展示效果 今天 ...

  5. Swift 4 无限滚动轮播图(UICollectionView实现)

    作为一个资深(自认为)iOS程序猿,会经常用到轮播图,但是总是感觉自己实现要比直接用别人的要方便一点,并且有一些需求需要深度定制,于是想着自己封装一个可以定制化的轮播图库JCyclePictureVi ...

  6. UICollectionView的无限滚动---妥协做法

    项目中总有写比较变态的需求,我们的UI设计师有喜欢很酷的交互,其中有个需求类似这种的 右侧部分是可以滚动的当然是无限滚动,这中效果只有UICollectionView的自定义布局才能实现,重写Layo ...

  7. 051_InfiniteScroll无限滚动

    1. InfiniteScroll无限滚动 1.1. InfiniteScroll无限滚动滚动至底部时, 加载更多数据. 1.2. Attributes 参数 说明 类型 默认值 infinite-s ...

  8. php jsonp实例 mip无限滚动组件接口注意事项

    在改造mip的过程中,很多同学遇到这样一个问题.mip无限滚动问题 异步请求数据接口(仅支持 JSONP 请求) 异步请求接口需要规范 callback 为 'callback' 那么什么是JSONP ...

  9. 当前元素_90行代码,15个元素实现无限滚动

    前言 在本篇文章你将会学到: IntersectionObserver API 的用法,以及如何兼容. 如何在React Hook中实现无限滚动. 如何正确渲染多达10000个元素的列表. 无限下拉加 ...

最新文章

  1. 微博登录界面的PHP代码,关于接入微博登录的代码实现
  2. jQuery选择器和选取方法 http://www.cnblogs.com/MaxIE/p/4078869.html
  3. Microsoft AJAX Client Library规范的实例
  4. 鸿蒙系统 产品,华为首款鸿蒙系统产品成行业公敌!只因开机无广告:遭十大品牌批评...
  5. java类和对象数组传参_Java 练习(替换数组元素, 将对象作为参数传递给方法)
  6. 存储网络性能岂能只靠“猜”
  7. APP性能测试-FPS测试
  8. 51单片机的家居空气质量监测系统proteus仿真设计
  9. PCB天线和陶瓷天线
  10. 关于vivo手机调试安装“解析程序包时出现问题”的解决方案
  11. 本地上传文件至Linux虚拟机报错“复制时发生出错“
  12. html 正则表达式 正整数,javascript如何判断数字是否为正整数?
  13. 拟物设计和Angular的实现 - Material Design (持续更新)
  14. python识别文字并且提示_Python识别文字,实现看图说话|CSDN博文精选
  15. 搞懂WebRtc,语音社交源码的开发就入门了!
  16. Python制作古诗生成器
  17. 1156 十个成绩排序
  18. 1、玩转树莓派音频——DIY具有输入输出的声卡(非USB)
  19. bidi(双向文字)与RTL布局总结
  20. 中新社、新华报业网等媒体报道腾云忆想云化IT生态,聚焦科技助力“双循环经济”数字化升级

热门文章

  1. Spark学习(一)—— 论文翻译
  2. An exception was thrown while activating Castle.Proxies.AppService测试AProxy -> Castle.Proxies.AppServ
  3. 关于亚马逊测评,你了解多少?
  4. 2020年最新整理20个必备PHP开源类库,武装生产力
  5. 用计算机图形画一个杯子,计算机图形学期末考试试卷(D卷)
  6. php 计算每月周期天数和剩余天数
  7. ubuntu系统下怎么对任意区域的截图
  8. windows7下安装vmware虚拟机linux系统详细流程
  9. 【多语言】PHP源码Strong Shop跨境电商系统源码/三语言/多货币附带搭建教程
  10. 免费移动办公平台app有哪些?一定要下载APP吗?