海屿文章列表

使用 CSS clip-path

images.jpg

CSS 中的 clip-path 属性允许我们指定展示一个元素的特殊区域。注意 clip属性已经被废弃。

使用剪切路径定义基本形状

使用clip-path可以轻松使用 CSS exclusion 模块中的polygon,ellipse,circle或inset关键字剪切基本形状。

多边形

多边形是所有可用形状中最灵活的,因为它允许您指定任意数量的点,有点像SVG路径。提供的点是可以任意单位(例如:基于像素或基于百分比)的X和Y坐标对。因为它是最灵活的,所以它也是最复杂的,你可能需要使用一些工具来定义这些点。

下面是一个简单的例子, clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)

你可以使用Clippy在线生成这些坐标。

自定义SVG形状

你还可以定义任何任意的SVG形状以用作剪切路径值。可以使用诸如Sketch之类的工具创建形状,然后将SVG标记复制到文本编辑器中。在SVG标记中,只需将形状包装在clipPath元素中,然后将clipPath包装在defs块中。

<svg width="0" height="0">
  <defs>
    <clipPath id="my-shape">
      <path d="M89.6342913,129 C86.6318679,137.611315 85,146.865086 85,156.5 C85,200.767808 119.448105,236.989829 163,239.821749 L163,300 L300,300 L300,163 L251.750745,163 C251.915896,160.855015 252,158.687329 252,156.5 C252,110.384223 214.615777,73 168.5,73 C146.712501,73 126.873981,81.3445721 112.006052,95.0121046 L64.5,0 L0,129 L89.6342881,129 Z">
      </path>
    </clipPath>
  </defs>
</svg>

现在,就可以使用url关键字和SVG形状的ID将定义的形状应用为剪切路径值:

.svg-shape {
  -webkit-clip-path: url(#my-shape);
  clip-path: url(#my-shape);
}

其实 clipPath元素可以包含任何数量的基本形状(如<rect><circle>等),<path><text>

clipPathUnits 属性

clipPathUnits 属性设置SVG坐标系统,值可以是 userSpaceOnUse 或 objectBoundingBox。

  • userSpaceOnUse 表示当前用户坐标系
  • objectBoundingBox 坐标系的原点位于剪切路径所应用于的元素的边界框的左上角,并且该边界框的宽度和高度相同。我们一般使用这个坐标系统,有点类似于百分比。

一般我们从矢量绘制工具里导出的都是用户坐标系统,我们可以在 https://yoksel.github.io/relative-clip-path/ 这里将坐标转换成 objectBoundingBox。

剪切路径的参考框

除了剪切路径本身之外,当所应用的剪切路径为时,还可以在属性中定义引用框。也就是说,使用基本形状函数之一创建的剪切路径。因此,仅为用作剪切路径的CSS形状指定参考框,而不为SVG指定。对于SVG ,引用框是HTML元素的边框。clip-path<basic-shape><clipPath><clipPath>

因此,为<basic-shape>剪辑路径指定了参考框。如果被修剪的元件是一个HTML元素,所述引用盒可以是四个基本盒模型框之一:margin-box,border-box,padding-box,或content-box。这些都是不言自明的。

如果将<basic-shape>剪辑路径应用于SVG元素,则可以将参考框设置为三个关键字值之一:

  • fill-box –使用对象边界框作为参考。
  • stroke-box –使用笔划边界框作为参考。
  • view-box –如果未viewBox指定,则使用最近的SVG视口作为参考框。如果viewBox确实指定了,则坐标系统将由所指定的原点和尺寸建立viewBox。

给裁剪加阴影

box-shadows肯定是不行的,clip-path将其截断。这里我们需要使用CSS filter 的 drop-shadow 函数实现。但是您不能直接在元素上使用它,因为clip-path也会将其切断,所以将其放在父元素上。只是 drop-shadow 的 spread-radius 大部分浏览器都不支持,但也基本满足了一般需求。
filter: drop-shadow(0 0 0.75rem crimson)


参考文章

https://alligator.io/css/clipping-with-clip-path/
https://www.sarasoueidan.com/blog/css-svg-clipping/
https://css-tricks.com/using-box-shadows-and-clip-path-together/

【译】Javascript 中的独立 UUID 生成器

这是一种在 Javascript 中生成UUID的简便方法(无外部依赖项)。

// Author: Abhishek Dutta, 12 June 2020
// License: CC0 (https://creativecommons.org/choose/zero/)
function uuid() {
  var temp_url = URL.createObjectURL(new Blob());
  var uuid = temp_url.toString();
  URL.revokeObjectURL(temp_url);
  return uuid.substr(uuid.lastIndexOf('/') + 1); // remove prefix (e.g. blob:null/, blob:www.test.com/, ...)
}

URL.createObjectURL() 静态方法会创建一个唯一的URL,以表示作为参数传递给它的对象。该uuid()方法使用此属性URL.createObjectURL()来在空对象上生成唯一的URL(这是我到目前为止测试过的所有浏览器中的随机UUID)。以下是此方法生成的一些示例UUID:

for(var i=0; i<10; ++i) { console.log(uuid()); }

f6ca05c0-fad5-46fc-a237-a8e930e7cb49
6a88664e-51e1-48c3-a85e-7bf00467e9e6
e6050f4c-e86d-4081-9376-099bfbef2c30
bde3da3c-b318-4498-8a03-9a773afa84bd
ba0fda03-f806-4c2f-b6f5-1e74a299e603
62b2edc3-b09f-4bf9-8dbf-c4d599479a29
e70c0609-22ad-4493-abcc-0e3445291397
920255b2-1838-497d-bc33-56550842b378
45559c64-971c-4236-9cfc-706048b60e70
4bc4bbb9-1e90-432b-99e8-277b40af92cd

注意:URL.createObjectURL()目标不是生成随机UUID。因此,上述生成UUID的方法可能会导致我尚未意识到的副作用。

为什么可以这样做,是因为blob URL生成UUID是规范的一部分。

原文地址:

https://abhishekdutta.org/blog/standalone_uuid_generator_in_javascript.html

【译】当你调用 console.log 时发生了什么

原文地址 https://keleshev.com/standard-io-under-the-hood

以V8为例,首先调用一个通用方法WriteToFile:

void D8Console::Log(const debug::ConsoleCallArguments& args,
                    const v8::debug::ConsoleContext&) {
  WriteToFile(nullptr, stdout, isolate_, args);
}

https://github.com/v8/v8/blob/4b9b23521e/src/d8-console.cc#L52-L55

在计算出Javascript的值之后,最终会调用fwrite。

void WriteToFile(const char* prefix, FILE* file, Isolate* isolate,
                 const debug::ConsoleCallArguments& args) {
  if (prefix) fprintf(file, "%s: "⁠, prefix);
  for (int i = 0; i < args.Length(); i++) {
    HandleScope handle_scope(isolate);
    if (i > 0) fprintf(file, " ");

    Local arg = args[i];
    Local str_obj;

    if (arg->IsSymbol()) arg = Local::Cast(arg)->Name();
    if (!arg->ToString(isolate->GetCurrentContext()).ToLocal(&str_obj)) return;

    v8::String::Utf8Value str(isolate, str_obj);
      int n = static_cast<int>(fwrite(*str, sizeof(**str), str.length(), file));

    if (n != str.length()) {
      printf("Error in fwrite\n");
      base::OS::ExitProcess(1);
    }
  }
  fprintf(file, "\n");
}

https://github.com/v8/v8/blob/4b9b23521e/src/d8-console.cc#L26

函数fwrite是C标准库(也称为libc)的一部分。在不同平台上有几种libc实现。在Linux上,流行的是glibc和musl。让我们用 musl。在那里,fwrite用C实现如下:

size_t fwrite(const void *restrict src, size_t size, size_t nmemb, FILE *restrict f)
{
    size_t k, l = size*nmemb;
    if (!size) nmemb = 0;
    FLOCK(f);
    k = __fwritex(src, l, f);
    FUNLOCK(f);
    return k==l ? nmemb : k/size;
}

https://github.com/bminor/musl/blob/05ac345f89/src/stdio/fwrite.c#L28-L36

进行一些间接操作后,这将调用一个实用程序函数__stdio_write,然后将进行(操作系统)系统调用 writev。

size_t __stdio_write(FILE *f, const unsigned char *buf, size_t len)
{
    struct iovec iovs[2] = {
        { .iov_base = f->wbase, .iov_len = f->wpos-f->wbase },
        { .iov_base = (void *)buf, .iov_len = len }
    };
    struct iovec *iov = iovs;
    size_t rem = iov[0].iov_len + iov[1].iov_len;
    int iovcnt = 2;
    ssize_t cnt;
    for (;;) {
          cnt = syscall(SYS_writev, f->fd, iov, iovcnt);

        /* … */
    }
}

https://github.com/bminor/musl/blob/05ac345f89/src/stdio/__stdio_write.c#L15

syscall这里的符号是一个宏,经过一些预处理程序后,它将扩展为__syscall3。

操作系统之间的系统调用不同,并且处理器体系结构之间的执行方式也不同。它通常需要编写(或生成)一些汇编。在x86-64上,musl定义__syscall3如下:

static __inline long __syscall3(long n, long a1, long a2, long a3)
{
    unsigned long ret;
    __asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
                          "d"(a3) : "rcx"⁠, "r11"⁠, "memory");
    return ret;
}

https://github.com/bminor/musl/blob/593caa4563/arch/x86_64/syscall_arch.h#L26-L32

这将设置系统调用号和参数。在x86-64上,调用进行系统调用的指令syscall。

进行系统调用后,控制将转移到(在这种情况下为Linux)内核。这就是另一码事了...

回顾 is-promise 库迁移 ESM 事故

is-promise 只做一件事,判断 JavaScript 对象是否为 Promise,但这个包被近百万个项目所依赖。作者在 4月25日发布的新版本并未遵循正确的 ES 模块标准,从而导致更新完成后,所有在构建时使用 is-promise 库的项目几乎全部报错。

这里犯了几个个错误 https://github.com/then/is-promise/blob/8e51d62bf158eb0685cd6109f0137472e8c3cb91/package.json#L7-L10

  • 以为 exports 和 main 一样,但实际上需要 ./ 前缀。
  • exports 不仅限制你能导入什么,还得限制你怎么导入

exports 字段

我们都知道 node 模块导出有 main ,所有版本的Node.js都支持,但是能力有限:它仅定义包的主要入口点。

"exports"扩展了 main,而且二者同时存在时,优先使用 exports。

Node.js支持以下条件:

  • "import" 通过import或 加载包时匹配import()。可以同时引用ES模块或CommonJS文件, import并且import()可以加载ES模块或CommonJS源。
  • "require"通过加载包时匹配require()。由于require()仅支持CommonJS,因此引用的文件必须为CommonJS。
  • "node"适用于任何Node.js环境。可以是CommonJS或ES模块文件。这种情况应该总是在"import"或 之后出现"require"。
  • "default"是CommonJS或ES模块文件,应始终排在最后。

条件导出

一个package.json文件可以直接定义单独的CommonJS和ES模块入口点:
条件匹配的规则是从上至下,所以应按对象顺序使用从最具体到最不具体的条件。

 "exports": {
    ".": [
      {
        "import": "./index.mjs",
        "require": "./index.js",
        "default": "./index.js"
      },
      "./index.js"
    ]
  },

嵌套条件

"exports": {
    "browser": "./feature-browser.mjs",
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    }
  }

参考文章:
https://github.com/then/is-promise/issues/13
https://medium.com/javascript-in-plain-english/is-promise-post-mortem-cab807f18dcc

访问者模式中的分派概念

images.png

静态绑定

静态绑定就是指在编译期就已经确定执行哪一个方法。函数的重载(方法名相同而参数不同)就是静态绑定的,重载时,执行哪一个方法在编译期就已经确定下来(编译时多态)。

动态绑定

动态绑定实在运行时判断所引用对象的实际类型,根据其实际的参数类型调用其相应的方法(运行时多态) 。

双分派(double dispatch)

分派过程就是确定一个方法调用的过程,双分派就是见人说人话,见鬼说鬼话,将静态绑定和动态绑定结合起来,也就是说根据运行时传入对象的类型确定方法调用的过程。
重载是静态绑定,多态是动态绑定(运行时进行),双分派把多态放在重载之前,以实现在运行时动态判断执行那个子类的方法。因为编译器知道this所指向的实例的类型,因此将其作为参数传入到函数中就可以实现。

class Man : public Base {
public:
    int getSpeakRes(SpeakBase& speak) {
        return speak.speakWord(this);  //编译器知道this是哪个类型实例
    }
}

class Ghost : public Base {
public:
    int getSpeakRes(SpeakBase& speak) {
        return speak.speakWord(this);
    }
}

class Speak : public SpeakBase {
public:
    int speakWord(Man& man) {
        return "Man"; 
    }
    int speakWord(Ghost& ghost) {
        return "Ghost"; 
    }
}
class Board {
    drawFigure(aFigure) {
        aFigure.getDrawnOn(this)
    }
    drawTriangle(aTriangle) {
        // ...
    }
    drawSquare(aSquare) {
        // ...
    }
}

class Triangle extends Figure {
    getDrawnOn(aDrawableSurface) {
        aDrawableSurface.drawTriangle(this)
    }
}

class Square extends Figure {
    getDrawnOn(aDrawableSurface) {
        aDrawableSurface.drawSquare(this)
    }
}


----

let board = new Board();
let triangle = new Triangle();

board.drawFigure(triangle)

参考文章:

https://www.cnblogs.com/loveis715/p/4423464.html
http://yaoyao.codes/c++/2015/04/26/cpp-double-dispatch
https://www.typescriptlang.org/docs/handbook/functions.html#overloads
https://www.mikealche.com/software-development/refactor-long-if-or-switch-blocks-with-double-dispatch-in-javascript