整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

现代CSS:纯 CSS 实现飘雪动画效果

天北京也飘雪 ❄️ 了! 在日常 Web 开发中实现飘雪效果时,我们首先想到的是通过 JavaScript 来实现。现代 CSS 这么强大,能否直接使用 CSS 来实现呢。经过调研后,鉴于现代 CSS 的强大,确实可以使用纯 CSS 来实现飘雪动画效果。

1.核心知识

CSS 关键帧和动画是实现飘雪动画的关键,使用动画延迟、位置变换可以让视觉感知更为明显。同时让图标独立于布局,可以实现雪花像真正的雪花一样飘动。

主要的雪花实现方案有:

1)HTML 元素:

使用多个 HTML 元素来绘制雪花,这个实现起来比较简单,使用雪花 ICON 或者雪花字符(44 或 ❄)来充当雪花,这种实现起来比较简单,看着会比较真实。

2)背景图片:

使用背景图片,即 CSS 的 radial-gradient() 函数来绘制雪花,这个可以实现对每个雪花的样式灵活可控

CSS radial-gradient() 函数创建一个 <image>,用来展示由 原点(渐变中心) 辐射开的颜色渐变。这个方法得到的是一个 CSS 数据类型的对象,这是一种特殊的 <image>

基本语法:

radial-gradient( [ x [, y] ] [ circle | ellipse ] [ extent-keyword ] at [ position ] , [color-stop [, length | persentage]]+ )

2.实现方案

本文采用 CSS radial-gradient() 来实现雪花,通过 html:5div.snow 来快速创建页面框架:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="snow"></div>
  </body>
</html>

2.1.实现雪花元素

为了效果更真实,同时给 .snow 元素增加了两个伪元素 ::before::after 来增强效果。

.snow,
.snow::before,
.snow::after {
  background-image: 
  radial-gradient( 4px 4px at 259px 455px, white 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 347px 72px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 345px 235px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(3px 3px at 152px 121px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient(5px 5px at 408px 540px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 6px 6px at 131px 307px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 100px 410px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(4px 4px at 88px 302px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient(4px 4px at 582px 533px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 5px 5px at 488px 476px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 20px 470px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(6px 6px at 303px 513px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 6px 6px at 50px 491px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 414px 30px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 287px 589px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 7px 238px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(5px 5px at 99px 579px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient(6px 6px at 118px 66px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient(5px 5px at 51px 396px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 6px 6px at 101px 21px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 228px 115px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(4px 4px at 74px 168px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 3px 3px at 158px 9px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 338px 147px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 567px 57px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 210px 99px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 338px 556px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 536px 350px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 281px 36px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 139px 151px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 494px 202px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 594px 262px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 338px 239px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 389px 480px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 85px 391px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 271px 181px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 425px 237px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 239px 399px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 387px 592px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 307px 461px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 458px 142px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(6px 6px at 593px 136px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 6px 6px at 458px 363px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 74px 549px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 543px 462px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 496px 547px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 239px 523px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(4px 4px at 216px 249px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 6px 6px at 120px 195px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 130px 551px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(6px 6px at 328px 533px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 6px 6px at 531px 461px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 58px 311px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(3px 3px at 139px 296px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 3px 3px at 57px 364px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 576px 512px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 248px 262px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 576px 244px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 452px 483px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 6px 6px at 154px 175px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(3px 3px at 593px 286px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 6px 6px at 255px 543px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 50px 443px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 23px 71px, rgba(255, 255, 255, 0.9) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 3px 3px at 182px 435px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 155px 384px, rgba(255, 255, 255, 0.8) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 5px 5px at 160px 594px, rgba(255, 255, 255, 0.7) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient(5px 5px at 213px 144px, white 50%, rgba(0, 0, 0, 0)), 
  radial-gradient( 3px 3px at 533px 373px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) ), 
  radial-gradient( 4px 4px at 519px 365px, rgba(255, 255, 255, 0.6) 50%, rgba(0, 0, 0, 0) );
}

2.2.实现动画

通过给伪元素 ::before::after 增加动画延迟、透明度、过滤效果,增加动画效果和层次。

.snow,
.snow::before,
.snow::after {
  animation: snow 3s linear infinite;
}

.snow::before {
  opacity: 0.4;
  animation-duration: 6s;
  animation-direction: reverse;
  filter: blur(3px);
}

.snow::after {
  opacity: 0.65;
  animation-duration: 9s;
  animation-direction: reverse;
  filter: blur(1.5px);
}

3.最终效果

最近有一个需求,在flink程序中接受一段java源代码,需要在flink程序中编译这段代码并执行,返回执行结果。

很多编译方案都是将java文件和生成的class文件放在临时磁盘上,但flink程序作为一个长期运行的任务,如果监控发现不及时,很可能出现临时目录满了的情况,所以最好做成一个纯内存编译,提升任务的稳定性。

解决方案

jdk中有JavaCompiler接口用来编译java源码,其中getTask方法用来生成一个编译任务。其中的参数,compilationUnits用来表示需要编译的内容;JavaFileManager用来管理各种文件;options用来传递编译的参数(比如-classpath/-d等javac命令的参数)。

所有的文件都用JavaFileObject来表示,因此需要实现自己的类,用内存保存java源码,用内存保存生成的class文件内容。

用内存保存java源码

private static class StringSourceSimpleJavaFileObject extends SimpleJavaFileObject {
        private final String code;

        StringSourceSimpleJavaFileObject(String name, String code) {
            super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }

        @Override
        public String toString() {
            return code;
        }
    }

用内存保存class文件

private static class OutputClassJavaFileObject extends SimpleJavaFileObject {
        private final ByteArrayOutputStream outputStream;

        protected OutputClassJavaFileObject(String className, Kind kind) {
            super(URI.create("mem:///" + className.replace('.', '/') + kind.extension), kind);
            this.outputStream = new ByteArrayOutputStream();
        }

        public OutputStream openOutputStream() throws IOException {
            return this.outputStream;
        }

        public byte[] getBytes() {
            return this.outputStream.toByteArray();
        }
    }

自定义FileManager

需要一个自定义的FileManager,当生成class文件时,使用自定义类。

需要注意的时,编译一个源码文件时,可能会因为内部类等原因,会生成多个class文件。

private static class FileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
        private Map<String, OutputClassJavaFileObject> outputs = new LinkedHashMap<>();

        FileManager(StandardJavaFileManager target) {
            super(target);
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) {
            final OutputClassJavaFileObject file = new OutputClassJavaFileObject(className, kind);
            outputs.put(className, file);
            return file;
        }

        public byte[] getOutputClassBytes(String className) {
            OutputClassJavaFileObject file = outputs.get(className);
            if (file != null) {
                return file.getBytes();
            }

            return null;
        }

        public Map<String, byte[]> getOutputClassBytes() {
            Map<String, byte[]> result = new LinkedHashMap<>();
            for (Map.Entry<String, OutputClassJavaFileObject> entry : outputs.entrySet()) {
                byte[] bytes = entry.getValue().getBytes();
                if (bytes != null && bytes.length > 0) {
                    result.put(entry.getKey(), bytes);
                }
            }
            return result;
        }
    }

调用编译任务

public static byte[] compile(String className, String classSource, String classPath) {
        logger.info("compile, className = {}, classPath = {}, sourceLength = {}", className, classPath, classSource.length());
        long start = System.currentTimeMillis();
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        JavaFileObject file = new StringSourceSimpleJavaFileObject(className, classSource);
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
        StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
        FileManager fileManager = new FileManager(standardFileManager);
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
        StringWriter out = new StringWriter();
        Iterable<String> options = null;
        if (classPath != null && classPath.length() > 0) {
            options = Arrays.asList("-cp", classPath);
        }
        JavaCompiler.CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, compilationUnits);
        boolean success = task.call();
        byte[] result = null;
        if (success) {
            result = fileManager.getOutputClassBytes(className);
            logger.info("compile, ok, className = {}", className);
        } else {
            logger.error("compile, error, className = {}", className);
            logger.error("compile, error, className = {}, diagnostics = {}", className, formatDiagnostics(diagnostics));
        }

        logger.info("compile, className = {}, success = {}, cost = {} ms, byteCodeLength = {}", className, success,
                System.currentTimeMillis() - start, result == null ? 0 : result.length);

        return result;
    }

参考

https://stackoverflow.com/questions/7989135/is-it-possible-to-programmatically-compile-java-source-code-in-memory-only

https://docs.oracle.com/javase/7/docs/api/javax/tools/JavaCompiler.html

|烧脑广告(shukewenzhai)

转眼间,就进入了12月,2020年只剩下了不到一个月的时间。回望这一年,感觉格外特殊和短暂。每年年末都充满了总结和感慨,这一年里,你经历了什么?收获了什么?改变了什么?

在年末这一节点,百度旗下的人工智能小度联合代理商鲸梦发布了一支温情短片《一人,一屋,一年度》,记录了一位年轻人2020年的点滴生活,送给那些在大城市独自打拼的年轻人。

详情复制链接打开:https://v.qq.com/x/page/b3207rt8nup.html

这支短片采用了固定画幅,画面只呈现了一扇窗户、一张书桌、一个小度,通过主角的第一视角回顾了一位年轻人2020年里经历的无聊、学习、挫折、坚持、努力......

2020年的年初,很多人和片中的主人公一样,因为疫情被困在家里,百无聊赖中开始学习烹饪等技能。到年中时,疫情得到控制,人们生活走向正规,开始找回工作状态,却因为各种原因而面对和承受挫败和压力。下半年时,有些人失败了离职了,但有些人仍然在坚持,或是找到了方向。

也许独自在大城市生活,真的很辛苦。但既然都到这了,就再往前走走看吧。说不定走着走着,就看见花开了。

2020年是特殊的一年,这一年有太多大事件带给人们无可言说的感动和感慨。以往的年末,许多品牌的年度广告都倾向于用宏观角度回顾一年的现象和精彩,更不用说今年。但小度这支年度广告,却用微观视角聚焦到了真实个体上,回看了一个普通人的一年。

日历翻回到2020年1月,窗外飘着细雪,她端着一杯不再热乎的水呆呆地看着小度正播报的新闻,后悔着不该不回家过年。到2月时,在家待久无聊了,她开始跟着视频学起了烹饪,可自己的厨艺天分并没有想象的那么好,还是在深夜里吃着泡面看剧更适合自己。4月时,窗外的树叶绿了,天空放晴了,她也终于能开心地去上班了。

年中时,在无数个只有小度的音乐陪伴的深夜,加班的她开始自我怀疑。没了工作的她,打算离开这座大城市,就像窗外的树叶离开树一样。但不甘心的她还是觉得坚持一下,也不知道是不是因为受了当时热播的《三十而立》的影响。她为自己添置了更多的陪伴,决定再好好拼一把。最终,她成功做出了一个小蛋糕,也算是为2020年画上了一个句号吧。

这支短片没有配音,只有一扇传递时间流逝的窗,一个没有露脸的人,一张摆着杂物的桌子,和几句简单的文案,却能让广告外的我们不自觉地联想到主人公当时的情绪和想法。因为,广告中所呈现的这些事都能让我们感同身受、引发共鸣——宅家、学习、加班、辞职、拼搏......这支短片里的主人公没有露脸,反而更让人有代入感。那个看着窗外的视角,就像自己的眼睛,我们在眼前这张桌上吃饭刷剧加班累倒。

窗外是有千万人来来往往的大世界,窗内只是藏着自己喜怒哀乐的小家。这个小家或许很朴素,一张桌子上摆着书籍、香薰、小盆栽,窗户上贴着写有小目标的便利贴,还有一只小猫和一台小度陪伴自己。这些小确幸填满了这间屋子,也填满了每个在大城市奋斗的年轻人的生活和心灵。

“一人、一屋、一年度”,这支短片中虽然呈现的东西很少也很简单,但让人感受到的情绪和共鸣却很多很复杂。

标题的“一人、一屋、一年度”一语双关,既呈现了一个普通人在一间屋子里度过的一年,也呈现了一年中,小度在这间屋子里如何陪伴一个人,以此来诠释品牌 slogan“小度在家,陪伴在家”的含义。

此外,小度还邀请知名插画师寂地为自己描绘了一组插画,寂地温暖的画风描绘了有小度陪伴的四季美好。

春天里万物苏醒,

小懒虫也该动起来做点事了

一个人的夏夜

听着小度播放的抒情音乐,

放松了心情

金黄的秋天

通过小度和爸妈视频,

表示收到了家里寄来的温暖

飘雪的冬天

和爱人相拥看一个电影,

小度让俩人凑得更近了

自今年品牌升级以来,小度始终在强调“陪伴”这一概念。无论是短片还是系列插画,小度的产品一直放置在桌子的一角,并根据不同情节和场景呈现出了多样的产品功能。在不同的时间和生活中,小度都自然地融入其中,成为了一个安静的陪伴者。四季改变,但小度的陪伴一直不变。

看到这里,或许你已然明白也认可了小度的这支简单却温暖的年度短片。这支年度短片不像一般意义上的年度短片那般恢宏,但它记录了万千普通人平凡的精彩。之所以小度要选择这样的方式,是因为它只想要传递出“小度在家,陪伴在家”的信念。

这支短片捕捉了大多数在大城市打拼的年轻人的生活日常,平淡的重复中又有着些许温暖的小确幸。小度默默地立在桌子上,成为生活中的小确幸,一直陪伴着主人,就像这支年度短片一样的安静简单。

小度想要带给人们这样一种无压力的轻声陪伴。

这支广告按品牌产品的露出程度来说,已经能算是一直强植入的广告了,但却没有违和感和不适感。

1月时,小度播放着疫情新闻;2月,小度播放着烘培教程视频;4月,小度显示着天气气温;6月,小度播放着舒缓的音乐;8月,小度和主人深夜对话;9月,小度实时更新节气壁纸;10月,小度推荐了热播电视剧的海报......

在这无数个时刻,小度不再只是一个冰冷的人工智能机器,而是能给予陪伴的小度。

短片最后的小彩蛋,呈现出了真实的小度。它或许不能像电影里那般智能高效,但也是这些小不足,让小度更具有真实感和亲切感。连人工智能都没法做到完美无缺,会犯小错误,会有做不到的事,你也不要太辛苦自己啦。

新的一年,再继续加油吧!