整合营销服务商

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

免费咨询热线:

刷爆朋友圈的“七彩祥云”,其实是这样生成的

刷爆朋友圈的“七彩祥云”,其实是这样生成的

/羊城派记者 陈亮

图/陈亮 赵思颖

6月28日傍晚,广州多地均可见一片七彩的云朵,犹如肥皂泡漂浮于白色的云层之间,在夕阳余晖的映照下发出绚丽多姿的光芒,持续了大约20分钟,非常罕见且极具观赏性。记者日前采访了中山大学大气科学学院副教授伊炳祺,原来,这种像彩虹一样的美丽云霞学名叫“虹彩云”(iridescence, irisation, rainbow clouds)。

据伊炳祺老师观察,云中出现五颜六色的色彩,有时色彩是混合在一起的,有时则是平行带状出现在云的边缘(例如云顶部)。其中,绿色和粉色较为常见。云中的虹彩最常出现在靠近太阳附近的地方,而且在太阳被遮挡住的情况下最为清晰可见,但在距离太阳较远的地方也是可以出现的。

图/陈亮 赵思颖

这种“天降祥云”的大气现象是怎么产生的呢?伊炳祺老师指出,太阳光的衍射和干涉现象是虹彩出现的主要原因,其中在相对太阳10度角以内,衍射起主要作用;大于10度,干涉往往是主要影响因素。

图/陈亮 赵思颖

伊炳祺老师进一步分析称,这些虹彩色是扭曲了的、或破碎的华现象(同样由于太阳光的衍射现象所引起)。在大气中的云滴或冰晶粒子尺寸比较一致的时候,规则的华现象才会出现。

当云发生生消变化时,这些色彩也会随之扭曲变形或消失。高积云、卷积云,尤其是荚状云最容易出现虹彩现象。虹彩现象也经常出现在云正在生成的时候,因为此时云滴粒子常常是具有相近的尺寸,而且此时云并不太厚。

图/陈亮 赵思颖

在大家的印象中,这样的“七彩祥云”似乎并不多见。但伊炳祺老师却告诉记者,相比于出现在接近极地地区的贝母云(nacreous, mother-of-pearl clouds)(特殊的虹彩云),虹彩云并不算非常罕见。实际上,虹彩云在全年、各个纬度、各种天气条件下都可以出现。尤其在炎热、湿润的下午天更容易出现,甚至在火箭发射飞行的尾迹云中也曾观察到有虹彩出现。

参考资料:

  1. https://cloudatlas.wmo.int/irisation-or-iridescence.html
  2. https://www.atoptics.co.uk/droplets/irid1.htm
  3. http://rammb.cira.colostate.edu/dev/hillger/optical-phenomena.htm

(更多新闻资讯,请关注羊城派 pai.ycwb.com)

来源 | 羊城派

责编 | 江文华 实习生 | 周子厚

| 王成

本文转载自SegmentFault

WebP格式介绍

WebP是Google开发的一种新的图片格式,它支持有损压缩、无损压缩和透明度,压缩后的文件大小比JPEG、PNG等都要小。所以可以节省带宽,减少页面载入时间,节省用户的流量。

Android和iOS的App只要引入Google提供的解码库,都可以很轻松的支持WebP格式。不过在Web上,WebP的支持还不是很广泛。根据Can I Use的数据,目前只有Chrome、Opera浏览器,以及Android的WebView是支持WebP的。但是WebP图片有这么多优点,我们能不能在Web页面中使用呢?可以。这篇文章就来讨论一下这个问题。

把已有的图片转换为WebP格式

要使用WebP格式,需要将你网站用到的图片都制作一份WebP格式的版本,如果你使用CDN服务商,它们一般都会提供转码到WebP格式的选项。如又拍云:

增加这样的配置后,我们可以通过给图片URL加上相应的后缀,来使用WebP格式的版本资源。

你也可以使用Webpack、Gulp的插件来批量转换图片格式。这里不赘述。

在浏览器中使用WebP格式

因为不是所有浏览器都支持WebP格式,我们就有两种思路:一个是只在支持WebP格式的浏览器中使用WebP格式;一个是让不支持WebP格式的浏览器可以支持WebP。

姿势一: <Picture>标签

<Picture>是HTML5中的一个新标签,类似<Video>它也可以指定多个格式的资源,由浏览器选择自己支持的格式进行加载。

<picture class="picture">

<source type="image/webp" srcset="image.webp">

<img class="image" src="image.jpg">

</picture>

如果浏览器支持WebP格式,就会加载Image.webp,否则会加载Image.jpg。

即使浏览器不支持<Picture>标签,图片仍然会正常显示,只是CSS可能无法正确选取到Picture元素。比如在IE8中,下面的CSS就不会起作用:

.picture img { width: 100px; height: 100px;}

但是可以这样来给图片写样式:

.image { width: 100px; height: 100px;}

即使浏览器使用的是WebP格式的图片,最终还是会应用img元素的样式。

不过只要使用了HTML5Shiv,使旧的浏览器支持这个标签,CSS选择器就可以正常使用了。这种方法是最简单的,但是不能作用于CSS中的图片(如背景)。

姿势二:使用JS替换图片的URL。

我们有很多的页面往往会用到图片的“懒加载”——通常是把图片的URL放在Img元素的一个自定义属性中,然后用JS在适当的时机将URL赋值给SRC属性。用类似的原理,我们可以根据浏览器是否支持WebP格式,给Img元素赋予不同的SRC值。

首先我们需要用JS来判断浏览器是否支持WebP格式,方法是给浏览器一个WebP格式的图片,看浏览器能否正确渲染。这种方法是异步的,所以需要把后续的操作写在回调函数中。我们可以将结果存储在LocalStorage中,这样之后就不用再次检查了。

function checkWebp(callback) { var img=new Image();

img.onload=function () { var result=(img.width > 0) && (img.height > 0);

callback(result);

};

img.onerror=function () {

callback(false);

};

img.src='data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA';

}

然后用下面的代码来根据是否支持WebP替换相应的SRC。

function showImage(useWebp){ var imgs=Array.from(document.querySelectorAll('img'));

imgs.forEach(function(i){ var src=i.attributes['data-src'].value; if (useWebp){

src=src.replace(/\.jpg$/, '.webp');

}

i.src=src;

});

}

checkWebp(showImage);

这种方式的优点是可以与已有的懒加载函数相结合。而且使用JS,我们还可以处理CSS中的图片(如背景图等)。

姿势三:使用JS解码WebP图片

既然WebP的解码器是开源的,那么能否用JS来实现呢?当然可以,有人就用JS写出了WebP的解码器。引入这个JS库,就是将所有的WebP图片用JS解码后转换为Base64,然后替换掉原来的URL,这样就可以让原本不支持WebP的浏览器正常显示WebP了。这个库的使用方法非常简单,看网页的说明即可。

这种方法的缺点是,因为JS要解码WebP图片,需要在此异步请求SRC中的URL(不过因为图片本身之前被下载了一次,直接使用了缓存);而且JS解码比较慢,对性能有影响,可能需要一段时间才能显示出图片来。

以上就是在浏览器中使用WebP图片的几种方法,可以根据自己的实际情况选用。在我们的实践中,使用了WebP格式后,图片的体积普遍缩小了1/3以上,既加快了加载的速度,还节省了用户的流量,我们十分推荐从现在就开始使用这种格式。

又小拍也关注WebP一段时间呢,不但关注了WebP,还关注了动态WebP,不久之后会有惊喜带给大家哦。

又拍云的处理功能实在太丰富,在图片处理方面,略缩图任意尺寸更改,全网一键更新所有图片,打水印,URL防盗链等。最近上线的又拍直播云,除了直播加速、推拉流外,更具有丰富的美颜、滤镜、水印、防盗链、鉴黄、禁播等功能,帮助直播平台快速上线直播业务,快来试试吧~

inIO

MinIO 是一款高性能、分布式的对象存储系统。它是一款软件产品, 可以100%的运行在标准硬件上。即X86等低成本机器也能够很好的运行MinIO。

MinIO与传统的存储和其他的对象存储不同的是:它一开始就针对性能要求更高的私有云标准进行软件架构设计。因为MinIO一开始就只为对象存储而设计。所以他采用了更易用的方式进行设计,它能实现对象存储所需要的全部功能,在性能上也更加强劲,它不会为了更多的业务功能而妥协,失去MinIO的易用性、高效性。 这样的结果所带来的好处是:它能够更简单的实现具有弹性伸缩能力的原生对象存储服务。

MinIO在传统对象存储用例(例如辅助存储,灾难恢复和归档)方面表现出色。同时,它在机器学习、大数据、私有云、混合云等方面的存储技术上也独树一帜。当然,也不排除数据分析、高性能应用负载、原生云的支持。

在中国:阿里巴巴、腾讯、百度、中国联通、华为、中国移动等等9000多家企业也都在使用MinIO产品。

安装

使用如下命令快速安装一个单机minio

# 下载 minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
# 添加可执行权限
chmod +x minio
# 设置登录minio的 access key
export MINIO_ACCESS_KEY=minioadmin
# 设置登录minio的 secret key
export MINIO_SECRET_KEY=minioadmin
# 启动 minio
./minio server /data

安装后使用浏览器访问http://IP地址:9000,如果可以访问,则表示minio已经安装成功。输入上面自定义的access key 和 secret key就可以登录了。

登录右下角加号创建mybucket桶

开放 mybucket 读写权限

创建项目操作 MinIO

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>io.minio</groupId>
	<artifactId>minio</artifactId>
	<version>7.0.1</version>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>LATEST</version>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.6</version>
</dependency>

编辑配置文件application.yml修改MinIO相关配置


server:
  port: 8080

spring:
  application:
    name: minio-test

  thymeleaf:
    cache: false

  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB

# minio 连接参数
minio:
  endpoint: 192.168.20.111
  port: 9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucketName: mybucket

连接 MinIO 配置

@Data
@ConfigurationProperties(prefix="minio")
@Component
public class MinioProp {
	private String endpoint;
	private String accesskey;
	private String secretKey;
	private int port;
}

创建 MinioClient

@Component
public class MinioConfiguration {

	@Autowired
	private MinioProp minioProp;

	@Bean
	public MinioClient minioClient() throws InvalidPortException, InvalidEndpointException {
		//MinioClient client=new MinioClient(minioProp.getEndpoint(), minioProp.getAccesskey(), minioProp.getSecretKey());
		MinioClient client=MinioClient.builder().endpoint(minioProp.getEndpoint(), minioProp.getPort(), false).credentials(minioProp.getAccesskey(), minioProp.getSecretKey()).build();
		return client;
	}
}

MinIO 查看桶列表,存入,删除 操作 MinioController

@Slf4j
@RestController
public class MinioController {
    @Autowired
    private MinioClient minioClient;
 
    private static final String MINIO_BUCKET="mybucket";
 
    @GetMapping("/list")
    public List<Object> list(ModelMap map) throws Exception {
        Iterable<Result<Item>> myObjects=minioClient.listObjects(MINIO_BUCKET);
        Iterator<Result<Item>> iterator=myObjects.iterator();
        List<Object> items=new ArrayList<>();
        String format="{'fileName':'%s','fileSize':'%s'}";
        while (iterator.hasNext()) {
            Item item=iterator.next().get();
            items.add(JSON.parse(String.format(format, item.objectName(), formatFileSize(item.size()))));
        }
        return items;
    }
 
    @PostMapping("/upload")
    public Res upload(@RequestParam(name="file", required=false) MultipartFile[] file) {
        Res res=new Res();
        res.setCode(500);
 
        if (file==null || file.length==0) {
            res.setMessage("上传文件不能为空");
            return res;
        }
 
        List<String> orgfileNameList=new ArrayList<>(file.length);
 
        for (MultipartFile multipartFile : file) {
            String orgfileName=multipartFile.getOriginalFilename();
            orgfileNameList.add(orgfileName);
 
            try {
                InputStream in=multipartFile.getInputStream();
                minioClient.putObject(MINIO_BUCKET, orgfileName, in, new PutObjectOptions(in.available(), -1));
                in.close();
            } catch (Exception e) {
                log.error(e.getMessage());
                res.setMessage("上传失败");
                return res;
            }
        }
 
        Map<String, Object> data=new HashMap<String, Object>();
        data.put("bucketName", MINIO_BUCKET);
        data.put("fileName", orgfileNameList);
        res.setCode(200);
        res.setMessage("上传成功");
        res.setData(data);
        return res;
    }
 
    @RequestMapping("/download/{fileName}")
    public void download(HttpServletResponse response, @PathVariable("fileName") String fileName) {
        InputStream in=null;
        try {
            ObjectStat stat=minioClient.statObject(MINIO_BUCKET, fileName);
            response.setContentType(stat.contentType());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
 
            in=minioClient.getObject(MINIO_BUCKET, fileName);
            IOUtils.copy(in, response.getOutputStream());
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            if (in !=null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
    }
 
    @DeleteMapping("/delete/{fileName}")
    public Res delete(@PathVariable("fileName") String fileName) {
        Res res=new Res();
        res.setCode(200);
        try {
            minioClient.removeObject(MINIO_BUCKET, fileName);
        } catch (Exception e) {
            res.setCode(500);
            log.error(e.getMessage());
        }
        return res;
    }
 
    private static String formatFileSize(long fileS) {
        DecimalFormat df=new DecimalFormat("#.00");
        String fileSizeString="";
        String wrongSize="0B";
        if (fileS==0) {
            return wrongSize;
        }
        if (fileS < 1024) {
            fileSizeString=df.format((double) fileS) + " B";
        } else if (fileS < 1048576) {
            fileSizeString=df.format((double) fileS / 1024) + " KB";
        } else if (fileS < 1073741824) {
            fileSizeString=df.format((double) fileS / 1048576) + " MB";
        } else {
            fileSizeString=df.format((double) fileS / 1073741824) + " GB";
        }
        return fileSizeString;
    }
}

Res返回数据封装


@lombok.Data
@AllArgsConstructor
@NoArgsConstructor
public class Res implements Serializable {
	private static final long serialVersionUID=1L;
	private Integer code;
	private Object data="";
	private String message="";
}

路由文件 RouterController

@Controller
public class RouterController {
	@GetMapping({"/", "/index.html"})
	public String index() {
		return "index";
	}

	@GetMapping({"/upload.html"})
	public String upload() {
		return "upload";
	}
}

启动类

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan(basePackages="com.minio")
public class ServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServerApplication.class, args);
	}

}


前端 列表页面 src\main\resources\templates\index.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8"/>
    <title>图片列表</title>
    <link rel="stylesheet" href="http://cdn.staticfile.org/element-ui/2.13.1/theme-chalk/index.css">
</head>
<body>
 
<div id="app">
 
    <el-link icon="el-icon-upload" href="/upload.html">上传图片</el-link>
    <br/>
 
    <el-table :data="results" stripe style="width: 60%" @row-click="preview">
        <el-table-column type="index" width="50"></el-table-column>
        <el-table-column prop="fileName" label="文件名" width="180"></el-table-column>
        <el-table-column prop="fileSize" label="文件大小"></el-table-column>
        <el-table-column label="操作">
            <template slot-scope="scope">
                <a :href="'/download/' + scope.row.fileName + ''" class="el-icon-download">下载</a>
                <a :href="'/delete/' + scope.row.fileName + ''" @click.prevent="deleteFile($event,scope.$index,results)"
                   class="el-icon-delete">删除</a>
            </template>
        </el-table-column>
    </el-table>
 
    <br/>
    <el-link icon="el-icon-picture">预览图片</el-link>
    <br/>
    <div class="demo-image__preview" v-if="previewImg">
        <el-image style="width: 100px; height: 100px" :src="imgSrc" :preview-src-list="imgList"></el-image>
    </div>
 
</div>
 
<script src="http://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script>
<script src="http://cdn.staticfile.org/axios/0.19.2/axios.min.js"></script>
<script src="http://cdn.staticfile.org/element-ui/2.13.1/index.js"></script>
 
<script>
    new Vue({
        el: '#app',
        data: {
            bucketURL: 'http://192.168.20.111:9000/mybucket/',
            previewImg: true,
            results: [],
            imgSrc: '',
            imgList: []
        },
        methods: {
            init() {
                axios.get('/list').then(response=> {
                    this.results=response.data;
                    if (this.results.length==0) {
                        this.imgSrc='';
                        this.previewImg=false;
                    } else {
                        for (var i=0; i < this.results.length; i++) {
                            this.imgList.push(this.bucketURL + this.results[i].fileName);
                            if (i==0) {
                                this.imgSrc=this.bucketURL + this.results[0].fileName;
                            }
                        }
                    }
                });
            },
            preview(row, event, column) {
                this.imgSrc=this.bucketURL + row.fileName;
                this.previewImg=true;
            },
            deleteFile(e,index,list) {
                axios.delete(e.target.href, {}).then(res=> {
                    if (res.data.code==200) {
                        this.$message('删除成功!');
                        list.splice(index, 1);
                        this.previewImg=false;
                    } else {
                        this.$message('删除失败!');
                    }
                });
            }
        },
        mounted() {
            this.init();
        }
    });
</script>
 
</body>
</html>

前端上传页面 src\main\resources\templates\upload.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>图片上传</title>
    <link rel="stylesheet" type="text/css" href="http://cdn.staticfile.org/webuploader/0.1.5/webuploader.css">
    <script type="text/javascript" src="https://cdn.staticfile.org/jquery/3.5.0/jquery.min.js"></script>
    <script type="text/javascript" src="http://cdn.staticfile.org/webuploader/0.1.5/webuploader.min.js"></script>
</head>
 
<body>
 
<div id="uploader-demo">
    <div id="fileList" class="uploader-list"></div>
    <div id="filePicker">选择图片</div>
</div>
<br/>
<a href="/index.html">返回图片列表页面</a>
 
<script type="text/javascript">
    var uploader=WebUploader.create({
        auto: true,
        swf: 'http://cdn.staticfile.org/webuploader/0.1.5/Uploader.swf',
        server: '/upload',
        pick: '#filePicker',
        accept: {
            title: 'Images',
            extensions: 'gif,jpg,jpeg,bmp,png',
            mimeTypes: 'image/*'
        }
    });
 
    uploader.on('fileQueued', function (file) {
        var $li=$(
            '<div id="' + file.id + '" class="file-item thumbnail">' +
            '<img>' +
            '<div class="info">' + file.name + '</div>' +
            '</div>'
            ),
            $img=$li.find('img');
 
        var $list=$("#fileList");
        $list.append($li);
 
        uploader.makeThumb(file, function (error, src) {
            if (error) {
                $img.replaceWith('<span>不能预览</span>');
                return;
            }
            $img.attr('src', src);
        }, 100, 100);
    });
 
    uploader.on('uploadProgress', function (file, percentage) {
        var $li=$('#' + file.id),
            $percent=$li.find('.progress span');
 
        if (!$percent.length) {
            $percent=$('<p class="progress"><span></span></p>')
                .appendTo($li)
                .find('span');
        }
        $percent.css('width', percentage * 100 + '%');
    });
 
    uploader.on('uploadSuccess', function (file) {
        $('#' + file.id).addClass('upload-state-done');
    });
 
    uploader.on('uploadError', function (file) {
        var $li=$('#' + file.id),
            $error=$li.find('div.error');
 
        if (!$error.length) {
            $error=$('<div class="error"></div>').appendTo($li);
        }
        $error.text('上传失败');
    });
 
    uploader.on('uploadComplete', function (file) {
        $('#' + file.id).find('.progress').remove();
    });
</script>
 
</body>
</html>

运行项目


列表页面

参考博客:

https://blog.csdn.net/jianzhang11/article/details/105672261