Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

学习CSS Houdini #65

Open
jyzwf opened this issue Feb 17, 2019 · 0 comments
Open

学习CSS Houdini #65

jyzwf opened this issue Feb 17, 2019 · 0 comments

Comments

@jyzwf
Copy link
Owner

jyzwf commented Feb 17, 2019

关于Houdini的印象还是在去年,然后也只是听过有这么个东西,但也只仅仅停留在听过。但为何今天会重新留意这个东西?因为闲来无聊,就去逛了某大佬的博客,逛着逛着,突然看到他写的一篇关于水波纹,重点不在于此,在于blog的最后来了句,用Houdini来实现,动画会更加容易。于是就打算看看这个东西。
关于水波纹的实现,到codeopen上一搜一大堆,直接来一个链接吧,Ripple Button。如果单用css来实现,可以考虑使用伪类加径向渐变,最后加点transition来实现,如果加上js来获取点击时的位置,就能做到更加好看的效果,这个不是文本的重点:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>水波纹</title>
        <style>
            button {
                display: inline-block;
                text-align: center;
                white-space: nowrap;
                cursor: pointer;
                border: none;
                padding: 8px 18px;
                margin: 10px 1px;
                font-size: 14px;
                font-weight: 500;
                text-transform: uppercase;
                background: transparent;
                background-color: #00bcd4;
                color: rgba(0, 0, 0, 0.87);
            }
            .ripple {
                position: relative;
                overflow: hidden;
            }

            .ripple:after {
                content: '';
                display: block;
                position: absolute;
                width: 100%;
                height: 100%;
                top: 0;
                left: 0;
                pointer-events: none;
                background-image: radial-gradient(
                    circle,
                    #666 10%,
                    transparent 10.01%
                );
                background-repeat: no-repeat;
                background-position: 50%;
                transform: scale(10, 10);
                opacity: 0;
                transition: transform 0.3s, opacity 0.5s;
            }

            .ripple:active:after {
                transform: scale(0, 0);
                opacity: 0.3;
                transition: 0s;
            }
        </style>
    </head>
    <body>
        <button class="button ripple">按钮</button>
    </body>
</html>

Houdini简介

总的来说Houdini就是开发者可以调用CSS的API,来扩展CSS,并且允许开发者参与到浏览器渲染引擎的样式和布局流程中。它提供了诸如painting APILayout APIparsing APITyped OM等API。接下来我们就来试试其中的paint api(由于有些浏览器暂不支持),主要参考官方提供的demo。

准备

Paint API 必须要在支持 https 服务器上或者本地 localhost 上才能使用。所以如果你是在本地开发,可以用 http-server 在本地快速搭建一个服务器。
要记得禁用浏览器缓存,让最新的 worklets 立马生效。
目前暂时无法在 worklets 中打断点或者插入 debugger ,不过 console.log() 还是可以用的。
这里我尝试了使用parcel来本地开发,但必须将worklet放置在dist目录下才能生效:
image
image

试试painting

同样我们实现其上面水波纹效果

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Document</title>
        <style>
            #ripple {
                width: 300px;
                height: 300px;
                border-radius: 150px;
                font-size: 5em;
                line-height: 300px;
                background-color: rgb(255, 64, 129);
                border: 0;
                box-shadow: 0 1px 1.5px 0 rgba(0, 0, 0, 0.12),
                    0 1px 1px 0 rgba(0, 0, 0, 0.24);
                color: white;

                --ripple-x: 0;
                --ripple-y: 0;  // 使用css 变量,在后面的worklet中可以被获取到
                --ripple-color: rgba(255, 255, 255, 0.54);
                --animation-tick: 0;
            }

            #ripple:focus {
                outline: none;
            }
            #ripple.animating {
                background-image: paint(ripple);   // 这里就是painting真正使用的地方,其中 ripple 是在worklet中定义的属性
            }
        </style>
    </head>
    <body>
        <div id="ripple">Houdini</div>

        <script src="./index.js"></script>
    </body>
</html>

下面是相关的动画实现,其中借用了 requestAnimationFrame

if (location.protocol === 'http:' && location.hostname !== 'localhost')
    location.protocol = 'https:';
if ('paintWorklet' in CSS) {
    CSS.paintWorklet.addModule('./app.js');
} else {
    document.body.innerHTML =
        'You need support for <a href="https://drafts.css-houdini.org/css-paint-api/">CSS Paint API</a> to view this demo :(';
}

const button = document.querySelector('#ripple');
let start = performance.now();
let x, y;
document.querySelector('#ripple').addEventListener('click', evt => {
    button.classList.add('animating');
    [x, y] = [evt.clientX, evt.clientY];
    start = performance.now();
    requestAnimationFrame(function raf(now) {
        console.log(start, now);
        const count = Math.floor(now - start);
        // 这里借用了css 变量,然后就可以在worklet中动态获取值
        button.style.cssText = `--ripple-x: ${x}; --ripple-y: ${y}; --animation-tick: ${count};`;
        if (count > 1000) {
            button.classList.remove('animating');
            button.style.cssText = `--animation-tick: 0`;
            return;
        }
        requestAnimationFrame(raf);
    });
});

重点来了,我们定义的paint api放在哪里呢?它不能直接像js一样被嵌入调用,而是需要使用worklet来帮助我们,像上面看的一样CSS.paintWorklet.addModule('./app.js');,在paintWorklet中添加一个我们自定义的脚本模块。worklet独立于主线程之外,不直接与DOM互动,特点为轻量且生命周期较短。
worklet生命周期
image

  1. Worklet生命周期从渲染引擎开始
  2. 渲染引擎启动js 主线程
  3. 然后将启动多个worklet进程,用于"存放"后续被加载进来的worklet。这些进程独立于主线程,这样就不会阻塞主线程
  4. 主线程开始加载js脚本
  5. js脚本调用worklet.addModule来异步加载worklet
  6. 一旦上述worklet加载完成,worklet就会被加载到两个或多个可用的worklet进程中
  7. 一旦在某处调用了在worklet中被注册的模块,渲染引擎就会执行该worklet中的处理函数。这个调用过程可以并发调用多个worklet实例

下面是我们注册的 ripple

registerPaint(
    'ripple',
    class {
        static get inputProperties() {
            return [
                '--ripple-color',
                '--animation-tick',
                'background-color',
                '--ripple-x',
                '--ripple-y',
            ];
        }
        paint(ctx, geom, properties) {
            console.log(ctx, geom, properties);

            const bgColor = properties.get('background-color').toString();
            const rippleColor = properties.get('--ripple-color').toString();
            const x = parseFloat(properties.get('--ripple-x').toString());
            const y = parseFloat(properties.get('--ripple-y').toString());
            let tick = parseFloat(
                properties.get('--animation-tick').toString()
            );

            if (tick < 0) tick = 0;
            if (tick > 1000) tick = 1000;

            ctx.fillStyle = bgColor;
            ctx.fillRect(0, 0, geom.width, geom.height);

            ctx.fillStyle = rippleColor;
            ctx.globalAlpha = 1 - tick / 1000;

            ctx.arc(x, y, (geom.width * tick) / 1000, 0, 2 * Math.PI);

            ctx.fill();
        }
    }
);

这里调用了registerPaint来注册我们需要的ripple,该函数由一个namepaint类构成,其中inputProperties 静态方法用来获取该元素上的一些css属性值,并且传入到paint方法中作为第三个参数。paint方法就是我们实际"绘画"的地方,它提供了三个参数:一个canvas的context变量、当前画布的大小即当前dom元素的大小,以及当前dom元素的css属性properties(在inputProperties返回的)。其中canvas的context变量没有实现canvas中例如对img的处理、drawFocusIfNeeded/scrollPathIntoView、fillText/strokeText、CanvasTextAlign/CanvasTextBaseline等的实现。总的来说就是利用canvas来进行绘图,这里就有想象力了。

layout api与paint的api编写方式基本一样,不过调用的是registerLayout,以及类里面的调用方法也有所不同,但浏览器的支持性不是那么好,试了一个官方提供的demo都没生效,-_-,还有 animation-worklet,直接告诉我不支持,用其polyfill也还是有bughoudini还是有一段路要走啊......

关于各个浏览器对Houdini的推进程度,看你点击如下链接查看:Is Houdini ready yet‽

至于其他的几个api以及houdini的现状,有兴趣的可以从下面的参考资料中了解:

参考资料:

Houdini:CSS 领域最令人振奋的革新
和 Houdini, CSS Paint API 打个招呼吧
Houdini Samples:houdini的demo
CSS-TAG Houdini Editor Drafts:houdini编辑草案
Houdini工作组规范

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant