分类目录归档:前端

前端包括Javascript和css等

react-native仿微信通讯录右侧边栏快速定位功能

代码地址 -> ReactNativeCountrySelect

1. 界面

SectionList把数据渲染出来

右边的A-ZText组件即可,这里为了做滑动定位,没有选择Touchable组件

import * as React from 'react';
import {
  Text,
  View,
  StyleSheet,
  SectionList,
  SafeAreaView,
} from 'react-native';

import countries from './countryCode.json';
const sectionMapArr = [
  ['A', 0],
  ['B', 1],
  ['C', 2],
  ['D', 3],
  ['E', 4],
  ['F', 5],
  ['G', 6],
  ['H', 7],
  ['I', 8],
  ['J', 9],
  ['K', 10],
  ['L', 11],
  ['M', 12],
  ['N', 13],
  ['O', 14],
  ['P', 15],
  ['Q', 16],
  ['R', 17],
  ['S', 18],
  ['T', 19],
  ['U', 20],
  ['V', 21],
  ['W', 22],
  ['X', 23],
  ['Y', 24],
  ['Z', 25],
];
export default class App extends React.Component {
  render() {
    return (
      <SafeAreaView style={styles.container}>
        <SectionList
          containerStyle={{ flex: 1, justifyContent: 'center' }}
          ItemSeparatorComponent={() => (<View style={{ borderBottomColor: '#F8F8F8', borderBottomWidth: 1, }} />)}
          renderItem={({ item, index, section }) => (
            <View style={styles.itemContainer}>
              <Text style={styles.itemText} key={index}>
                {item.countryName}
              </Text>
            </View>
          )}
          renderSectionHeader={({ section: { key } }) => (
            <View style={styles.headerContainer}>
              <Text style={styles.headerText}>{key}</Text>
            </View>
          )}
          sections={countries}
          keyExtractor={(item, index) => item + index}
        />
        <View
          style={{ width: 16, justifyContent: 'center' }}
        >
          {sectionMapArr.map((item, index) => {
            return (
              <Text
                key={index}
              >
                {item[0]}
              </Text>
            );
          })}
        </View>
      </SafeAreaView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFFFFF',
    flexDirection: 'row',
  },
  headerContainer: {
    padding: 5,
    backgroundColor: '#F8F8F8',
  },
  headerText: {
    fontWeight: 'bold',
  },
  itemContainer: {
    paddingHorizontal: 5,
    paddingVertical: 10,
  },
  itemText: {
    fontSize: 16,
  },
});

这时候界面已经完成了,然后就是增加触摸滑动定位的功能了。

2. 使用Gesture Responder System监听触摸事件

给右侧的View上启用手势


<View style={{ width: 16, justifyContent: 'center' }} onStartShouldSetResponder={() => true} onMoveShouldSetResponder={() => true} > {sectionMapArr.map((item, index) => { return ( <Text key={index} > {item[0]} </Text> ); })} </View>

这样我们就可以在触摸滑动的时候,获得滑动到的位置

3. 使用onLayout找到每个字母对应的X、Y

首先在constructor里声明一个实例属性,用来记录每个字母的信息:

this.ps = [];

然后在Text组件上利用onLayout获得每个字母的位置,并且存到this.ps里:

<Text
  key={index}
  onLayout={({
    nativeEvent: {
      layout: { x, y, width, height },
    },
  }) => {
    this.ps = this.ps.filter(i => i.key !== item[0]); 
    this.ps.push({
      key: item[0],     // 对应的字母 A-Z
      min: y,           // 字母顶部Y坐标
      max: y + height,  // 字母底部Y坐标
      index: item[1],   // 字母对应SectionList的index
    });
  }}
>
  {item[0]}
</Text>

4. 根据滑动找到滑到哪个字母上

<View
  style={{ width: 16, justifyContent: 'center' }}
  onStartShouldSetResponder={() => true}
  onMoveShouldSetResponder={() => true}
  onResponderMove={({ nativeEvent: { pageY } }) => {
    const offsetY = pageY - this.offsetY;
    const find = this.ps.find(
      i => i.min < offsetY && i.max > offsetY
    );
    if (find) {
      console.log(find) // 滑动到的字母
    }
  }}
>

5. 根据触摸的字母,SectionList跳到对应的位置

  1. 先在constructor里创建ref
this.sectionlist = React.createRef();
  1. 在SectionList上绑定ref
ref={this.sectionlist}
  1. 调用SectionList的scrollToLocation
onResponderMove={({ nativeEvent: { pageY } }) => {
  const offsetY = pageY - this.offsetY;
  const find = this.ps.find(
    i => i.min < offsetY && i.max > offsetY
  );
  if (find) {
    this.sectionlist.current.scrollToLocation({
      sectionIndex: find.index,
      itemIndex: 0,
      animated: false,
    });
  }
}}

完工

react native改变WebView背景颜色

react-native的WebView背景颜色是无法改变的,不过我们可以用另一个View盖住WebView,达到改变颜色的效果。

import React from 'react';
import {View, StyleSheet, WebView, ActivityIndicator} from 'react-native';

export default class YourComponent extends React.PureComponent{

    state = {
        loading: true,
    };

    render() {
        return (
            <View>
                <WebView
                    source={html}
                    onLoadEnd={() => {
                        this.setState({loading: false});
                    }}
                />
                {
                    this.state.loading && (
                        <View style={{
                            ...StyleSheet.absoluteFillObject,
                            backgroundColor: '#000000',  // your color 
                            alignItems: 'center',
                            justifyContent: 'center',
                        }}>
                            <ActivityIndicator />
                        </View>
                    )
                }
            </View>
        )

    }
}

Chrome中from memory cache和from disk cache

Chrome中文件缓存有Memory Cache和Disk Cache两种

顾名思义

  • Memory Cache:放在内存中的缓存

  • Disk Cache:放在硬盘上的缓存

一些规律:

  1. 第一次打开页面的时候,是没有缓存的,直接请求资源

  2. 刷新页面(⌘+R)的时候,会发现有些文件是from memory cache,有些是from disk cache

  3. 关掉浏览器,再打开页面,没有memory cache了

  4. 无痕窗口下,资源都会放在memory cache,关掉窗口缓存就没了,不会留下痕迹

  5. 图片会优先放进memory cache

  6. 小文件会优先放进memory cache

  7. 大文件几乎是disk cache

最后送上一张http缓存的图片

使用Gatsby生成静态网站并部署在GitHub上

什么是Gatsby

Blazing-fast static site generator for React

Gatsby是一个基于React极其快的静态网站生成工具

支持各种数据源,markdown、Wordpress等

创建网站

  1. 安装Gatsby命令行工具:
    npm install --global gatsby-cli
    
  2. 创建一个新的网站
    gatsby new sheng00.cn
    
  3. 运行刚才创建的网站
    cd sheng00.cn
    gatsby develop
    

    打开localhost:8000即可看到

部署在GitHub上

  1. 在GitHub上创建一个repository

  2. 在刚才生成的网站运行下面的命令

    git add -A
    git commit -m "first commit"
    git remote add origin git@github.com:shengoo/sheng00.cn.git
    git push -u origin master
    
  3. 安装gh-pages
    yarn add gh-pages
    
  4. 在package.json里增加一个脚本
    "deploy": "gatsby build --prefix-paths && gh-pages -d public"
    
  5. 部署到GitHub
    yarn deploy
    

设置自定义域名

  1. 从域名注册商那里,把域名指向yourusername.github.io

  2. 在GitHub的repository的设置里,设置Custom Domain:www.sheng00.cn

  3. 在Gatsby生成的网站中,新建一个目录static,创建一个文件CNAME

    www.sheng00.cn
    
  4. 再次部署
    yarn deploy
    

源代码网址:https://github.com/shengoo/sheng00.cn

在react应用中使用模块化css

什么是模块化CSS?

模块化管理CSS,避免全局污染,实现模块化、可服用。

在react中应用

启用CSS Modules

css-loader中增加一个选项:

{
    loader: require.resolve('css-loader'),
    options: {
        importLoaders: 1,
        minimize: true,
        sourceMap: shouldUseSourceMap,
        modules: true, // 启用CSS Modules
    },
},

CSS文件

app.css:

.title{
    color: red;
}

最后会编译成:

._2L1SLeGPg5sisdRmqO9mCH {
  color: red;
}

JavaScript文件

app.js:

import styles from './app.css';

class App extends Component {
  render() {
    return <div className={styles.title}>Hello World.</div>;
  }
}

最后会编译成:

<div class="_2L1SLeGPg5sisdRmqO9mCH">Hello World.</div>

fetch 用法简介

Fetch API提供了一个获取资源的接口(包括跨域)。任何使用过 XMLHttpRequest 的人都能轻松上手,但新的API提供了更强大和灵活的功能集。

参数

第一个参数是URL地址。

第二个参数(可选)是fetch使用的选项(method、headers等选项)

返回结果

fetch会返回一个promise对象,resolve对应请求的Response。

response属性:

  1. body
  2. bodyUsed

    一个布尔值来标示该Response是否读取过Body

  3. headers

    此Response所关联的Headers 对象.

  4. ok

    一个布尔值来标示该Response成功(状态码200-299) 还是失败.

  5. redirected

    该Response是否来自一个重定向,如果是的话,它的URL列表将会有多个

  6. status

    Response的状态码 (例如, 200 成功).

  7. statusText

    与该Response状态码一致的状态信息 (例如, OK对应200).

  8. type

    Response的类型 (例如, basic, cors).

  9. url

    Response的URL.

response方法

  1. arrayBuffer()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为ArrayBuffer格式的promise对象

  2. blob()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为Blob格式的promise对象

  3. formData()

    读取Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为FormData格式的promise对象

  4. json()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为JSON格式的promise对象

  5. text()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为USVString格式的promise对象

示例:读取一个图片并生成URL

var myImage = document.querySelector('.my-image');
fetch('flowers.jpg').then(function(response) {
  return response.blob();
}).then(function(response) {
  var objectURL = URL.createObjectURL(response);
  myImage.src = objectURL;
});

get请求

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });

post请求

fetch(url, {
  method: 'POST',
  body: JSON.stringify(data),
  headers: new Headers({
    'Content-Type': 'application/json'
  })
})

上传文件&提交表单

var formData = new FormData();
var fileField = document.querySelector("input[type='file']");

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/upload', {
  method: 'PUT',
  body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

移动端禁用和启用滚动、拖动

弹窗和弹框是网站应用中经常用到的技术,但是在页面弹出一个全屏的弹窗的时候,还是可以使用触摸来滑动弹窗下面的页面。

为了用户有更好的体验,可以使用以下代码,在弹窗的时候禁用滚动,弹窗关闭的时候再次启用滚动。

// left: 37, up: 38, right: 39, down: 40,
// spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
var keys = {37: 1, 38: 1, 39: 1, 40: 1};

function preventDefault(e) {
    e = e || window.event;
    if (e.preventDefault)
        e.preventDefault();
    e.returnValue = false;
}

function preventDefaultForScrollKeys(e) {
    if (keys[e.keyCode]) {
        preventDefault(e);
        return false;
    }
}

function disableScroll() {
    if (window.addEventListener) // older FF
        window.addEventListener('DOMMouseScroll', preventDefault, false);
    window.onwheel = preventDefault; // modern standard
    window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
    window.ontouchmove  = preventDefault; // mobile
    document.onkeydown  = preventDefaultForScrollKeys;
}

function enableScroll() {
    if (window.removeEventListener)
        window.removeEventListener('DOMMouseScroll', preventDefault, false);
    window.onmousewheel = document.onmousewheel = null;
    window.onwheel = null;
    window.ontouchmove = null;
    document.onkeydown = null;
}

网页适配 iPhoneX,就是这么简单(转载)

前言

iPhoneX 取消了物理按键,改成底部小黑条,这一改动导致网页出现了比较尴尬的屏幕适配问题。对于网页而言,顶部(刘海部位)的适配问题浏览器已经做了处理,所以我们只需要关注底部与小黑条的适配问题即可(即常见的吸底导航、返回顶部等各种相对底部 fixed 定位的元素)。

笔者通过查阅了一些官方文档,以及结合实际项目中的一些处理经验,整理了一套简单的适配方案分享给大家,希望对大家有所帮助,以下是处理前后效果图:

适配之前需要了解的几个新知识

安全区域

安全区域指的是一个可视窗口范围,处于安全区域的内容不受圆角(corners)、齐刘海(sensor housing)、小黑条(Home Indicator)影响,如下图蓝色区域:

也就是说,我们要做好适配,必须保证页面可视、可操作区域是在安全区域内。

更详细说明,参考文档:Human Interface Guidelines – iPhoneX

viewport-fit

iOS11 新增特性,苹果公司为了适配 iPhoneX 对现有 viewport meta 标签的一个扩展,用于设置网页在可视窗口的布局方式,可设置三个值:

  1. contain: 可视窗口完全包含网页内容(左图)
  2. cover:网页内容完全覆盖可视窗口(右图)
  3. auto:默认值,跟 contain 表现一致

注意:网页默认不添加扩展的表现是 viewport-fit=contain,需要适配 iPhoneX 必须设置 viewport-fit=cover,这是适配的关键步骤。

更详细说明,参考文档:viewport-fit-descriptor

env() 和 constant()

iOS11 新增特性,Webkit 的一个 CSS 函数,用于设定安全区域与边界的距离,有四个预定义的变量:

  • safe-area-inset-left:安全区域距离左边边界距离
  • safe-area-inset-right:安全区域距离右边边界距离
  • safe-area-inset-top:安全区域距离顶部边界距离
  • safe-area-inset-bottom:安全区域距离底部边界距离

这里我们只需要关注 safe-area-inset-bottom 这个变量,因为它对应的就是小黑条的高度(横竖屏时值不一样)。

注意:当 viewport-fit=contain 时 env() 是不起作用的,必须要配合 viewport-fit=cover 使用。对于不支持env() 的浏览器,浏览器将会忽略它。

在这之前,笔者使用的是 constant(),后来,官方文档加了这么一段注释(坑):

The env() function shipped in iOS 11 with the name constant(). Beginning with Safari Technology Preview 41 and the iOS 11.2 beta, constant() has been removed and replaced with env(). You can use the CSS fallback mechanism to support both versions, if necessary, but should prefer env() going forward.

这就意味着,之前使用的 constant() 在 iOS11.2 之后就不能使用的,但我们还是需要做向后兼容,像这样:

padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */

注意:env() 跟 constant() 需要同时存在,而且顺序不能换。

更详细说明,参考文档:Designing Websites for iPhone X

如何适配

了解了以上所说的几个知识点,接下来我们适配的思路就很清晰了。

第一步:设置网页在可视窗口的布局方式

新增 viweport-fit 属性,使得页面内容完全覆盖整个窗口:

<meta name="viewport" content="width=device-width, viewport-fit=cover">

前面也有提到过,只有设置了 viewport-fit=cover,才能使用 env()。

第二步:页面主体内容限定在安全区域内

这一步根据实际页面场景选择,如果不设置这个值,可能存在小黑条遮挡页面最底部内容的情况。

body {
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

第三步:fixed 元素的适配

类型一:fixed 完全吸底元素(bottom = 0),比如下图这两种情况:

可以通过加内边距 padding 扩展高度:

{
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

或者通过计算函数 calc 覆盖原来高度:

{
  height: calc(60px(假设值) + constant(safe-area-inset-bottom));
  height: calc(60px(假设值) + env(safe-area-inset-bottom));
}

注意,这个方案需要吸底条必须是有背景色的,因为扩展的部分背景是跟随外容器的,否则出现镂空情况。

还有一种方案就是,可以通过新增一个新的元素(空的颜色块,主要用于小黑条高度的占位),然后吸底元素可以不改变高度只需要调整位置,像这样:

{
  margin-bottom: constant(safe-area-inset-bottom);
  margin-bottom: env(safe-area-inset-bottom);
}

空的颜色块:

{
  position: fixed;
  bottom: 0;
  width: 100%;
  height: constant(safe-area-inset-bottom);
  height: env(safe-area-inset-bottom);
  background-color: #fff;
}

类型二:fixed 非完全吸底元素(bottom ≠ 0),比如 “返回顶部”、“侧边广告” 等

像这种只是位置需要对应向上调整,可以仅通过外边距 margin 来处理:

{
  margin-bottom: constant(safe-area-inset-bottom);
  margin-bottom: env(safe-area-inset-bottom);
}

或者,你也可以通过计算函数 calc 覆盖原来 bottom 值:

{
  bottom: calc(50px(假设值) + constant(safe-area-inset-bottom));
  bottom: calc(50px(假设值) + env(safe-area-inset-bottom));
}

你也可以使用 @supports 隔离兼容样式

写到这里,我们常见的两种类型的 fixed 元素适配方案已经了解了吧。如果我们只希望 iPhoneX 才需要新增适配样式,我们可以配合 @supports 来隔离兼容样式,当然这个处理对页面展示实际不会有任何影响:

@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
  div {
    margin-bottom: constant(safe-area-inset-bottom);
    margin-bottom: env(safe-area-inset-bottom);
  }
}

写在最后

以上几种方案仅供参考,笔者认为,现阶段适配处理起来是有点折腾,但是至少能解决,具体需要根据页面实际场景,在不影响用户体验与操作的大前提下不断尝试与探索,才能更完美的适配。

感谢您的阅读,本文由 凹凸实验室 版权所有。如若转载,请注明出处:凹凸实验室(https://aotu.io/notes/2017/11/27/iphonex/

通过修改react-scripts来自定义create-react-app的模板

create-react-app是一个无需任何配置就能轻松创建react应用使用的命令行工具,它主要是使用react-scripts来配置需要的webpackbabel等一系列工具。

react-scripts创建的应用可以满足大部分的需求,但是有时候我们需要修改或者创建自己的配置项。react-scripts提供了一个命令eject,使用eject命令可以将react-scripts内置的各种配置项暴露出来,这时候我们就可以通过更改配置文件。

eject可以让你自定义所有配置项。但是如果有很多相似的项目,建议fork一份react-scripts和其他需要的packges,做一个自己的模板使用。

自定义create-react-app的模板

Fork create-react-app

打开create-react-app,fork出自己的一份create-react-app

建议fork一个稳定的分支,master不是稳定的。

packages目录里,有一个目录react-scriptsreact-scripts这个目录里包含了buildteststart你的react app的脚本。

修改配置

把我们fork好的create-react-app clone到本地,checkout一个稳定版的tag,打开react-scripts/scripts/init.js这个文件。

在里面加一行console.log('Hello world.');

把我们自定义的react-scripts发布到NPM

先把react-scripts目录下的package.json文件里的name等字段的值改成我们自己的。

现在从命令行切换到react-scripts目录里,使用npm publish命令行进行发布。如果npm提示你登陆,就登陆上自己的npm账号。

测试我们自己的react-scripts

在命令行中使用我们自己的模板创建一个react-app

create-react-app test-app --scripts-version shengoo-react-scripts

可以看到,我们的增加的那行console.log生效了。

接下来就可以通过修改react-scripts增加我们自己需要的功能了。

css实现响应式正方形或固定宽高比


代码如下
HTML:

<div class="square">
  <div class="content">
    Hello!
  </div>
</div>

css:

.square {
  position: relative;
  width: 50%;
  border: 4px solid red;
}
.square:after {
  content: "";
  display: block;
  padding-bottom: 100%;
}
.content {
  position: absolute;
  width: 100%;
  height: 100%;
}

See the Pen jxXeEB by Qing Sheng (@shengoo) on CodePen.

原理如下

.square中创建一个伪元素,使用padding-bottom: 100%;,伪元素会用父级节点的宽度来计算100%,所以高度就是父级的宽度,这样就能实现正方形来。
同理,需要固定宽高比的情况下,都可以使用这种方式。