232019.8

手绘风格组件

Great minds discuss ideas;
Average minds discuss events;
Small minds discuss people

——埃莉诺·罗斯福(Eleanor Roosevelt)

09:11:14 已有0条回复
222019.8

webpack umd library only for root, replace exports

webpack 导出的 umd 模块在传入了 output.library 的情况下,会输出四个条件语句,大概如下:

if(typeof exports === 'object' && typeof module === 'object')
  module.exports = factory(require("a"), require("b"));
else if(typeof define === 'function' && define.amd)
  define(["a", "b"], factory);
else if(typeof exports === 'object')
  exports["o"] = factory(require("a"), require("b"));
else
  root["o"] = factory(root["a"], root["b"]);

这样的 umd 模式。其中,关于依赖、导出接口名都可以定制,其中依赖部分通过 externals 配置来定制,导出接口名通过 library 来定制。

但是,现在的一个情况是,在第三个条件句,即红色部分,这个部分会在标准的 commonjs 中被引用。所谓标准的 commonjs 是由 commonjs 官方定义的,只有 exports 和 require 两个关键字的模块方案。而 nodejs 虽然遵循 commonjs,但是在其基础上实现了 module 关键字,将 exports 作为 module.exports 的引用,因此被成为 commonjs2。

我们现在去看这个部分,倘若一个模块在遵循标准 commonjs 的情况下,导出如下:

// a.js
exports.a = function a() {}
// b.js
exports.b = function b() {}
// main.js
export.a = require('./a.js')
export.b = require('./b.js')

外部使用这个包,实际上应该是:

const { a, b } = require('./main.js')

但是在 webpack 的 umd 模块下使用时变成了:

// bundle.js
exports.o = factory()

那么在外面的其他程序去用这个包时就需要变成下面这种方式才可以:

const { o } = require('./bundle.js')
const { a, b } = o

这显然不符合我们的期望,我们希望即使 webpack 打包之后,仍然保持原有的使用方式。所以,我写了一个方法来修改这个部分的输出,经过处理之后,webpack 这个部分的输出将会是:

else if(typeof exports === 'object')
	{ var a = factory(require("a"), require("b")); for (var i in a) exports[i] = a[i]; }

即替换掉原来的输出形式,将原本的输出接口直接赋值到 exports 上,这样就保持了原本的逻辑。

具体做法如下:

// webpack.config.js
const { bufferify } = require('webpack-bufferify')
const plugins = [
  bufferify(function(content, file, assets, compilation, compiler) {
    if (file.split('.').pop() !== 'js') {
      return
    }
    const { optimization } = compiler.options
    content = content.toString()
    content = optimization.minimize === true
      ? content.replace(/exports\[.*?\]=(.*?):/, `function(e,a){for(var i in a)e[i]=a[i]}(exports,$1):`)
      : content.replace(/exports\[.*?\](.*?);/, `{ var a$1; for (var i in a) exports[i] = a[i]; }`)
    return content
  }),
]

module.exports = {
  ...,
  plugins,
}

webpack-bufferify 是我写的一个组件,用以替换 webpack 输出的结果内容。通过上面的处理,就可以实现我们的目的。

13:13:25 已有0条回复
122019.8

收集 scroll 事件

scroll 事件是不支持冒泡的,那么怎么收集呢?当然是在捕获阶段进行收集了。

document.addEventListener('scroll', (e) => {
  const target = e.target
  const isTop = Object.getPrototypeOf(target).constructor.name === 'HTMLDocument'
  const scrollLeft = isTop ? window.scrollX : target.scrollLeft
  const scrollTop = isTop ? window.scrollY : target.scrollTop
  console.log({ target, scrollLeft, scrollTop })
}, true)

这样就可以通过兼容方式收集到滚动事件的信息了。

11:22:46 已有0条回复
272019.7

文字旅行者:一种文本加载(类似打字机)效果

我的博客首页会在一段时间后加上一个作品区域,每个区域都会点缀一点点动效。在 Nautil 的部分,我想实现一个效果,就是在 Nautil 这个单词后面,不断切换 Nautilidae, Nautilus, Nautil.js 这几个单词,效果就像用键盘输入、删除再输入的效果。

在谷歌搜索了一段时间后,我确信没有一个库能够完完整整的实现这个效果。它们都实现了输入效果,但是没有回删效果。于是,我只能自己写一个 js 库来实现这个效果。最终的效果你可以在我的博客首页看到。但是,后来我觉得,这是一个非常不错的效果,虽然在我的博客上看到的是一个输入的效果,但是实际上,对它重新思考,会发现它比这个效果的想象空间大很多。你可以用它来断句、给视频打字幕、利用文字翻语音接口朗读小说。它像一个具有生命的灵活工具,因此,我称它为“文字旅行者”(github:TextTraveler)。

遍历文本怎么才能做到对开发者友好呢?我们以“视频字幕”来举例,如果你想写一个视频字幕,你会怎样?对于我来说,我想象中的写字幕的方式,就是在剧本中打断点。那么什么样的方式可以让我们打断点呢?我想到了 ES6 的字符串模板标签。于是,一个字幕效果就出来了:

`你話三年。${[0,100,1]}三年之後又三年,${[0,100,1]}三年之後又三年!${[0,100,1]}十年都嚟緊頭啦老細!${[0,1000]}`

通过在字符串模板中加入占位符,就可以给字符串打断点了。每个断点的地方加入参数,用以控制该断点前面的文字,将会以怎样的时间或行为出现在字幕中。

如何利用这些参数呢?

使用标签函数即可。我写了 travelText 这个标签函数,它可以生成一个 text traveler 用以处理每个段落。

const traveler = travelText`你話三年。${[0,100,1]}三年之後又三年,${[0,100,1]}三年之後又三年!${[0,100,1]}十年都嚟緊頭啦老細!${[0,1000]}`

接下来所有的事情就交给 traveler 去做。但他实际上只是一个记录者,你可以通过 on 方法监听他记录的内容,并作出相应的界面修改。

而为了更方便的实现我想要的那个效果,我又写了 createTextWriter 函数,它直接在一个 DOM 元素中不断的根据 traveler 的记录情况重写 html 中的文本,从而达到我想要的效果。

一切的一切都在 github 上。

 

12:38:24 已有0条回复
242019.7

图片的演算

222019.7

利用 Ps 获得文字或路径的 svg

今天有个需求,电脑上有一些特殊的字体(免费的),想把它们用到网页中,但是因为兼容性问题,所以想转化为 svg 来使用,做到最轻便。本来以为可以通过 Photoshop 直接导出文字的 svg,结果 ps 导出的 svg 是将图片 base64 后的 png 作为 svg 内嵌的图片,也是醉了。于是开始 google,很多文献都指向一条路,就是先在 ps 中抽离出路径,然后用 Illustrator 来导出 svg,但我没有装 illustrator,然后想装一个吧,结果 2g 的安装包,要下 1 天多。。。试了网上各种办法之后,最终找到了自己的办法。现在记录一下。

1.创建路径

如果直接在 ps 里面用路径相关的工具画路径,那就无所谓,随便画。如果是文字转路径,很简单,在图层上右键,选择 convert to shape 即可。这个时候,切换到路径面板,可以看到已有的路径。然后回到图层面板,把所有的图层删除干净,这样,就只保留了路径。如果路径和图层绑定,可以在路径面板复制一下该路径。

2.导出为 .ai

新版的 ps 可以直接导出为 .ai 文件。file -> export -> paths to illustrator

3.在线工具

这一招真的很神,在 google 里搜索 ai to svg,一大堆在线工具可以用,我用了这个可以成功转化。

另,由于 ai 实际上是 pdf,文档尺寸必须按照 pdf 的尺寸来,把要转化的元素放在左上角上,才能最好的控制路径的结果。

4.处理 svg

下载下来 svg 之后,你可以得到所有的路径,但是这些路径是分开的。我的文字,一个字就是一个 path,当然,对于字来说,这是好事,但是对于其他画来说就不是了,你要一个一个的去调试,找到哪一个 path 代表哪一个部分。

经过整理之后,我得到了:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path style="stroke:#000; fill:none" d="M 4.4891 43.676L 6.2891 43.676L 13.8491 5.75598L 26.2091 5.75598L 26.5691 4.07605L 0.0491 4.07605L -0.3109 5.75598L 12.0491 5.75598L 4.4891 43.676z"/>
<path style="stroke:#000; fill:none" d="M 18.4089 56.996C 23.5689 56.996 26.8089 52.076 29.0889 47.636L 47.0889 14.996L 45.2889 14.996L 35.0889 33.7161C 33.5289 36.5961 31.9689 39.4761 30.5289 42.2361L 30.2889 42.2361C 29.9289 39.5961 29.4489 36.4761 28.9689 33.7161L 25.9689 14.996L 24.2889 14.996L 29.0889 44.276L 27.7689 46.916C 25.2489 52.076 22.1289 55.436 18.5289 55.436C 17.8089 55.436 17.2089 55.316 16.4889 54.9561L 15.7689 56.396C 16.8489 56.876 17.8089 56.996 18.4089 56.996z"/>
<path style="stroke:#000; fill:none" d="M 58.8483 44.396C 66.0483 44.396 71.5683 39.8361 71.5683 32.876C 71.5683 28.556 69.0483 25.796 65.3283 23.636L 60.1683 20.636C 57.4083 19.076 54.8883 16.916 54.8883 13.076C 54.8883 9.11609 58.3683 4.91595 63.7683 4.91595C 68.0883 4.91595 71.3283 7.07605 73.3683 9.71606L 74.6883 8.63599C 72.6483 5.75598 68.9283 3.35608 64.1283 3.35608C 57.5283 3.35608 53.0883 8.03595 53.0883 13.316C 53.0883 17.516 56.0883 20.276 59.3283 22.196L 64.6083 25.316C 67.7283 27.1161 69.7683 29.516 69.7683 33.1161C 69.7683 38.996 65.0883 42.8361 59.0883 42.8361C 53.9283 42.8361 50.3283 40.676 47.8083 36.8361L 46.3683 37.796C 49.0083 41.996 53.2083 44.396 58.8483 44.396z"/>
<path style="stroke:#000; fill:none" d="M 77.2077 43.676L 78.8877 43.676L 83.2077 22.196C 87.7677 17.756 91.3677 15.8361 94.0077 15.8361C 97.8477 15.8361 99.4077 17.756 99.4077 21.4761C 99.4077 22.676 99.2877 23.516 98.9277 25.316L 95.2077 43.676L 96.8877 43.676L 100.608 25.076C 100.968 23.396 101.088 22.676 101.088 21.2361C 101.088 17.1559 99.1677 14.276 94.2477 14.276C 90.7677 14.276 87.5277 16.556 83.6877 19.796L 83.4477 19.796L 84.1677 17.0359L 87.5277 0.355957L 85.8477 0.355957L 77.2077 43.676z"/>
<path style="stroke:#000; fill:none" d="M 107.087 32.636C 107.087 40.316 111.287 44.396 117.887 44.396C 121.007 44.396 124.007 42.9561 126.287 41.276L 125.447 39.9561C 123.287 41.516 120.647 42.8361 118.007 42.8361C 112.007 42.8361 107.387 38.756 109.127 29.1559L 129.647 29.1559C 130.007 27.9561 130.367 26.276 130.367 24.3561C 130.367 18.4761 127.967 14.276 121.847 14.276C 114.527 14.276 107.087 21.7161 107.087 32.636zM 109.487 27.5961C 111.047 20.756 116.327 15.8361 121.727 15.8361C 126.887 15.8361 128.687 19.916 128.687 23.996C 128.687 25.316 128.567 26.396 128.327 27.5961L 109.487 27.5961z"/>
<path style="stroke:#000; fill:none" d="M 134.926 43.676L 136.726 43.676L 142.246 16.076C 142.846 12.8361 143.446 8.99597 144.046 5.75598L 144.286 5.75598L 145.726 14.276L 150.046 39.1161L 152.086 39.1161L 165.646 14.276L 170.326 5.75598L 170.566 5.75598C 169.846 8.87598 169.006 12.8361 168.286 16.076L 162.766 43.676L 164.566 43.676L 172.486 4.07605L 169.486 4.07605L 156.286 27.9561L 151.606 36.9561L 151.366 36.9561L 149.806 27.9561L 145.846 4.07605L 142.846 4.07605L 134.926 43.676z"/>
<path style="stroke:#000; fill:none" d="M 185.326 44.396C 193.126 44.396 200.326 36.2361 200.326 25.556C 200.326 18.3561 196.606 14.276 190.846 14.276C 183.046 14.276 175.846 22.436 175.846 33.1161C 175.846 40.316 179.566 44.396 185.326 44.396zM 185.446 42.8361C 180.406 42.8361 177.526 39.3561 177.526 32.756C 177.526 23.636 184.246 15.8361 190.726 15.8361C 195.766 15.8361 198.646 19.316 198.646 25.916C 198.646 35.0359 191.926 42.8361 185.446 42.8361z"/>
</svg>
22:17:54 已有0条回复
192019.7

一张图认识25个私募基金术语

132019.7

TySheMo 2.0 发布

TySheMo 2.0 主要更新了两个功能,一个是输出的错误日志,另一个是新增试验中的 Parser 功能。

上一个版本在进行数据类型校验的时候,在遇到第一个校验失败时,就会停止继续校验,这对于对象类型的校验会导致开发者不知道真实的错误包含了那些,只知道第一个错误,因而不能有针对性的一次性修改完毕。而 2.0 则增强了日志输出功能,在进行对象、数组校验时,会遍历所有的节点,把每一个节点上如果存在的错误一次性全部返回,并且提供了 format 方法来让开发者可以自定义输出的内容。

第二个功能是新增的试验性功能:Parser。这个功能的主要作用是对数据类型进行文本描述。在上一个版本中,我们必须通过内置的方式创建一个类型容器来进行数据类型校验。但是在一些需要跨端通信的情况下,我们无法提供这种能力。Parser 则是提供这种能力的一个尝试。假设你的前端应用在某些情况下,需要从后台拉取一个类型配置,这个时候,就可以用上文本描述。后台返回一个基于字符串的 json 文件,在前端通过 Parser 对 json 进行解析之后,得到一个前端可用的类型容器。这就是新增的 Parser 功能。但这还是一个试验性的功能,虽然可用,但还未到非常标准的程度,希望后续可以将这个功能及其附带的需求完成。

22:01:16 已有0条回复
072019.7

学不动系列:新前端框架 Nautil,哇~

基于 react 的除了 Next.js 其他的所谓框架我都只想说,鸡你太美!React 实在太香了,但是实战开发起来却又不怎么好弄。让我们来看最新的 roadmap:

来自:react-developer-roadmap

我就想写个网页,何必这么残忍?面对这个画面,我都有贴大哥表情包的冲动。但是我忍住,毕竟我还是想写代码来丰富我的人生的。

在 react 的生态中,我们不难发现非常优秀的项目。例如,跨组件的通信怎么办?来 redux 吧!redux 组织好复杂?上 redux-saga 吧!异步咋解决呢?来 redux-thunk 真香!使用特定 state 也挺麻烦?搞 reselect 如何!哎呀呀,还是来 rematch 或来套国产的 dva 也行……

你让我写个应用,我除了要花精力去解决打包编译工具的问题之外,还要来纠结到底要用哪个方案。真的很烦唉!其实,就像当年用 jquery 用爽了,扛起 backbone 或更狠上 angular 就开干。我写了这么多年 react,想要的是一套框架,拿过来就开撸的那种。

从 roadmap 中,我们看这个区域,也就是 react 生态的状态管理和表单数据管理这个部分。是不是复杂!很复杂!那是因为 MobX 还没有扩展开来聊。害得我又想贴表包……

我们能不能简化 react 应用中的状态管理?能不能把数据请求这块处理的更加优雅?能不能提供更靠谱的 form 模型?有的。

Nautil 框架来了!!!

一款基于 react 的 js 前端框架。在我看来,一个前端框架需要具备应用开发中必不可少的部件。而且要好用,方便开发者理解和写代码。Nautil 一次性提供体验超爽,而且还有趣的:

  • UI 渲染
  • 路由
  • 状态管理
  • 数据仓库
  • 数据类型检查
  • 跨端开发解决方案
  • 多语言国际化

让我们来举个例子,就拿复杂的 state 管理来开刀吧。想想你在以往经验里面使用 redux 是怎么用的,有没有在准备方案阶段就很纠结和心累?如果你用 Nautil,不需要纠结,因为你没得选,只有一种全局的状态管理方案。

import { Component, Store, ObservableProvider } from 'nautil'
import { Section, Text } from 'nautil/components'

// create a store
const store = new Store({
   name: 'tomy',
   age: 10,
})

class App extends Component {
  render() {
    return (
      <ObservableProvider
        name="$store" value={store}
        subscribe={dispatch => store.watch('*', dispatch)} dispatch={this.update}
      >
        <Page1></Page1>
      </ObservableProvider>
    )
  }
}

class Page1 extends Component {
  static injectProviders = {
    $store: true,
  }
  render() {
    const { state } = this.$store
    return <Section>
      <Text>Hi, I am {state.name}, and I am {state.age} years old.</Text>
    </Section>
  }
}复制代码

啥?你没看到怎么管理状态的?不怪你,因为它实在实在是太方便了,因为在 Nautil 里面,你可以没有全局的状态管理,但是你一定会有某个数据是全局的(准确的说是跨组件),你只需要用 ObservableProvider 这个组件去提供就可以了。然后在很深的组件里面去使用 injectProviders 来注入这个被提供的数据。恰巧的是,Nautil 提供的 Store 是一个可被观察的数据容器,使用 store.watch 来监听它的数据的变化,并在变化的时候触发更新操作。

还没明白?

这里的 store 就是你的状态管理器了啊!!!store 里面存着整个应用被共享的 state,你可以在任何地方去,任何地方改,任何地方删,都会通过 store.watch 的部分触发应用更新。也许你没听明白我的意思。我的意思是,你甚至可以在 react 应用之外去修改数据都是可以的,只要你在任何地方执行一下:

store.state.age ++复制代码

你的界面就会发生变化。是的,即使把你的 nautil 应用和 angular 应用混在一起,共享一个 store,也是可以的。同时,你还可以通过 watch 来收集每一次数据的变化,在必要时,把收集起来的数据通过 store.update 来复原数据。

它是不是完全超出了你对 react 状态管理的理解?没关系,还有一个东西会超出你的理解,那就是从后台 api 拉取数据。

你有没有想过,为什么那么优秀的 redux 会变得那么臃肿?因为数据是前端应用的命啊,一个不需要从后台 api 取数据的前端应用,除非是工具或游戏,否则就是没有灵魂的应用啊!所以,redux 出来之后,包括 react 本身,都必须面临异步数据请求的问题。以 react 本身而言,它一开始完全没有机制去处理,一个数据必然存在两种状态:数据还没有从后台拉回来的状态,已经拉回来的状态。在数据没有拉回来的时候,把界面显示出来,等数据回来了,再闪一下,哦豁,用户都可以化身产品经理给开发提 bug 了。

Nautil 怎么解决?

import { Component, ObservableProvider, Depository, Prepare } from 'nautil'
import { Text } from 'nautil/components'

// set data sources information
const datasources = [
  {
    id: 'articles',
    url: '/api/articles',
  },
  {
    id: 'tag',
    url: '/api/tags/{tag}',
  },
]

// create a data depository
const depo = new Depository({
   expire: 10000,
})

// register data sources into depository
depo.register(datasources)

class App extends Component {
  render() {
    return (
      <ObservableProvider
        name="$depo" value={depo}
        subscribe={dispatch => depo.subscribe('articles', dispatch).subscribe('tag', dispatch)}
        dispatch={this.update}
      >
        <Page1></Page1>
      </ObservableProvider>
    )
  }
}

class Page1 extends Component {
  static injectProviders = {
    $depo: true,
  }

  render() {
    const depo = this.$depo
    const some = depo.get('tag', { tag: 'some name' })

    return (
      <Prepare isReady={some} loadingComponent={<Text>loading...</Text>}>
        <Text>{some.name}</Text>
      </Prepare>
    )
  }
}复制代码

创建一个数据仓库来管理从后台 api 接口拉取的数据。在业务代码和后台 api 之间,不要直接打交道,而是通过数据仓库整合。业务代码,只需要从仓库中 get 数据即可,这个 get 是同步操作,不需要等待。同时,仓库是可观察的,通过一个 subscribe 方法对仓库进行观察,如果发现对应的数据发生变化,那么立即更新界面。对于仓库中还没有对应的数据时,使用 Prepare 组件来提供一个 loading 效果。

听上去好像还挺顺的对不对?但是,等等!!!我什么时候发 ajax 去请求数据?

你真的不需要关心 ajax 的问题,真的!你只要 get, get, get~ 我能理解你理解不了,只是现在。只要你用用,什么 thunk, saga, action, dispatch 统统一边去耍吧。不需要异步的好吗。

话说回来,即使有异步操作,我们还有 store,随时随地,时时刻刻,想改就改,毫无限制。

如果你再去了解一下 Nautil 的路由,你会发现一个规律:

Nautil 就是 react,nautil 不只是 react。

说的这么玄乎,意思是,它完全兼容 react 应用。比如你在其他地方写了一些纯 UI 的 react 组件,没关系,拿过来直接用。或者你想在其他的 react 应用中使用 nautil 编写的组件,没关系,直接拿去用。

你会发现,nautil 中强调“可观察的”这样一个概念。简单说就是有一个办法知道发生了变化。nautil 内置的 Observer 组件用于监听这些变化,并且在变化发生时执行传入的逻辑(一般是更新界面)。所以,在 nautil 中,数据、状态、路由都是“可观察的”对象,被注入到应用中。但本质上,它们是完全独立的,意思是,你可以在 react 应用之外的任何场景使用这些“可观察的”对象,也可以将整个体系之外的“可观察的”对象拿到 nautil 中直接使用。这也可以说是“渐进式”,可用可不用,当然,作为框架,你必须这样用才符合我写 nautil 的初衷。

不开源的都是耍流氓。

很遗憾,Nautil 到现在还没有发布。但你可以通过 github 关注或贡献代码。你也可以从 github 克隆下来跑跑看,也许会喜欢上呢~

github

最后补充一句。Nautil 还提供了内置的 Model,拥有结构化、数据校验、格式化、类型检查、可观察等特性,在表单开发时可能是你正在寻找的最好的解决方案,之一。

作者:否子戈
链接:https://juejin.im/post/5d20ce44f265da1ba328e6f7
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

09:59:43 已有0条回复