这个图库是来自历年官方活动或者联名活动的表情包,有动态有静态。































以下是我自己编辑的表情包,主要是修改B站的静态表情包。





















网站将逐步切换到WebP格式图片,今天捣鼓了插件在服务器端替换图片,但WP的媒体库却怎么都搞不定了,媒体库会自动生成很多缩略图用于不同的场景,我不想碰它的缩略图生成效果,因此只写单一的转换代码是没法做出完整的效果的。
退而求其次使用本地对图片进行处理,该脚本使用PIL库,图片分辨率限制为2560最长/宽,可以处理带透明通道的图片,也可以处理GIF,用下来效果还不错。
1月8日更新:
【Python】使用WebP官方库进行WebP转换
【Python】使用cwebp、gif2webp、exiftool实现保留exif信息的WebP转换

import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageSequence
import os
def resize_image(img):
max_size = 2560
width, height = img.size
if width > max_size or height > max_size:
if width > height:
new_width = max_size
new_height = int((new_width / width) * height)
else:
new_height = max_size
new_width = int((new_height / height) * width)
img = img.resize((new_width, new_height), Image.LANCZOS)
return img
def convert_to_webp(input_path):
try:
file_extension = os.path.splitext(input_path)[1].lower()
output_path = os.path.splitext(input_path)[0] + ".webp"
if not os.path.exists(input_path):
raise FileNotFoundError(f"文件 {input_path} 不存在,请检查路径。")
if file_extension == '.webp':
return f"文件 {input_path} 已是 WebP 格式,无需转换。"
with Image.open(input_path) as img:
if file_extension in ['.gif'] and getattr(img, "is_animated", False):
frames = []
durations = []
for frame in ImageSequence.Iterator(img):
# 处理透明度
if frame.mode == "P":
frame = frame.convert("RGBA")
# 转换帧为 RGBA 并存储
new_frame = frame.copy()
frames.append(new_frame)
durations.append(frame.info.get('duration', 100))
# 重复最后一帧
if len(frames) > 1:
durations[-1] = max(durations[-1], 100)
# 保存为动态 WebP
frames[0].save(
output_path,
format="WEBP",
save_all=True,
append_images=frames[1:],
duration=durations,
loop=img.info.get('loop', 0), # 循环次数
transparency=0, # 确保透明度保留
quality=85
)
else:
# 静态图片处理
if img.mode == "P":
if "transparency" in img.info:
img = img.convert("RGBA")
else:
img = img.convert("RGB")
img = resize_image(img)
if img.mode != "RGBA":
img = img.convert("RGBA")
img.save(output_path, format="WEBP", quality=85)
return f"图片已转换并保存为 {output_path}"
except Exception as e:
return f"处理文件时发生错误: {e}"
def select_files():
file_paths = filedialog.askopenfilenames(
title="选择图片文件",
filetypes=[("所有图片格式", "*.jpg;*.jpeg;*.png;*.gif;*.webp;*.bmp;*.tiff"),
("JPEG 图片", "*.jpg;*.jpeg"),
("PNG 图片", "*.png"),
("GIF 图片", "*.gif"),
("WebP 图片", "*.webp"),
("BMP 图片", "*.bmp"),
("TIFF 图片", "*.tiff")]
)
if file_paths:
for path in file_paths:
file_listbox.insert(tk.END, path)
def convert_and_save_batch():
files = file_listbox.get(0, tk.END)
if not files:
messagebox.showerror("错误", "请选择至少一个图片文件!")
return
results = []
for file_path in files:
result = convert_to_webp(file_path)
results.append(result)
messagebox.showinfo("完成", "\n".join(results))
def clear_list():
file_listbox.delete(0, tk.END)
root = tk.Tk()
root.title("批量图片转换为 WebP 工具")
root.geometry("600x400")
frame = tk.Frame(root)
frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL)
file_listbox = tk.Listbox(frame, selectmode=tk.EXTENDED, yscrollcommand=scrollbar.set)
scrollbar.config(command=file_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
button_frame = tk.Frame(root)
button_frame.pack(pady=10)
select_button = tk.Button(button_frame, text="选择文件", command=select_files, width=15)
select_button.grid(row=0, column=0, padx=5)
clear_button = tk.Button(button_frame, text="清空列表", command=clear_list, width=15)
clear_button.grid(row=0, column=1, padx=5)
convert_button = tk.Button(button_frame, text="批量转换", command=convert_and_save_batch, width=15)
convert_button.grid(row=0, column=2, padx=5)
root.mainloop()
(!该内容目前只做测试,分类未经过严格验证)
通过模板函数引入ECharts的js,不过这次限制了只有“Echarts”标签的文章加载,避免影响其他界面的加载速度。
效果如上:采用了矩形树图+旭日图,感觉旭日图更加醒目,通过点击标题来切换图表类型。
<div id="chart-title" style="text-align: center; font-size: 20px; margin-top: 10px; cursor: pointer; color: #007BFF;">
切换图表类型
</div>
document.getElementById('chart-title').addEventListener('click', function () {
if (currentOption === 'treemap') {
myChart.setOption(sunburstOption);
currentOption = 'sunburst';
} else {
myChart.setOption(treeMapOption);
currentOption = 'treemap';
}
});
});
网站此前用的回到顶部按钮是通过插件市场中的插件实现,效果还不错,不过功能比较单一。
我在其他的博客中看到了一个可以在页面下拉过程中实现当前页面进度的回顶按钮,觉得实用性很棒,因此尝试通过页面脚本的形式导入。
但是这种方式过于低效,且没法很好自定义,所以便尝试是否可以通过插件的形式导入功能,通过和GPT的一番交流,大致明白了WP插件的制作过程。
WP的插件可以通过Zip文件导入,其中的结构为:
backtop/
├── assets/
│ ├── backtop.css
│ └── backtop.js
├── backtop.php
└── readme.txt (可选)
其中backtop.php是插件的核心文件,包含了插件的主要功能和初始化代码。
backtop.css为样式表,js则是JavaScript 文件,写交互逻辑使用。
参考代码:
PHP:
<?php
/*
Plugin Name: 写你的插件名称
Description: 描述
Version: 版本号
Author: 作者
Author URI: https://wanxuefeiyang.cn
License: GPL2
*/
// Enqueue CSS and JS,注意地址
function backtop_enqueue_assets() {
wp_enqueue_style('backtop-style', plugin_dir_url(__FILE__) . 'assets/backtop.css');
wp_enqueue_script('backtop-script', plugin_dir_url(__FILE__) . 'assets/backtop.js', [], false, true);
$options = get_option('backtop_options');
wp_localize_script('backtop-script', 'backTopOptions', [
'position' => $options['position'] ?? 'bottom-right',
'color' => $options['color'] ?? '#000000',
'size' => $options['size'] ?? '40px',
'shape' => $options['shape'] ?? 'circle',
'margin' => $options['margin'] ?? '20px 20px'
]);
}
add_action('wp_enqueue_scripts', 'backtop_enqueue_assets');
// 插入 HTML
function backtop_render_html() {
echo '
<div id="backtop-tool">
<ul>
<li id="backtop" class="hidden">
<span id="backtop-percentage">0%</span>
</li>
</ul>
</div>';
}
add_action('wp_footer', 'backtop_render_html');
// 设置页面,可以自定义一些内容
function backtop_add_settings_page() {
add_options_page(
'BackTop Settings',
'BackTop',
'manage_options',
'backtop-settings',
'backtop_render_settings_page'
);
}
add_action('admin_menu', 'backtop_add_settings_page');
function backtop_render_settings_page() {
?>
<div class="wrap">
<h1>BackTop Settings</h1>
<form method="post" action="options.php">
<?php
settings_fields('backtop_options_group');
do_settings_sections('backtop-settings');
submit_button();
?>
</form>
</div>
<?php
}
function backtop_register_settings() {
register_setting('backtop_options_group', 'backtop_options', [
'type' => 'array',
'sanitize_callback' => 'backtop_sanitize_options',
'default' => [
'position' => 'bottom-right',
'color' => '#000000',
'size' => '40px',
'shape' => 'circle',
'margin' => '20px 20px'
],
]);
add_settings_section('backtop_main_section', 'Main Settings', null, 'backtop-settings');
add_settings_field('position', 'Position', 'backtop_position_field', 'backtop-settings', 'backtop_main_section');
add_settings_field('color', 'Background Color', 'backtop_color_field', 'backtop-settings', 'backtop_main_section');
add_settings_field('size', 'Button Size', 'backtop_size_field', 'backtop-settings', 'backtop_main_section');
add_settings_field('shape', 'Shape', 'backtop_shape_field', 'backtop-settings', 'backtop_main_section');
add_settings_field('margin', 'Margin', 'backtop_margin_field', 'backtop-settings', 'backtop_main_section');
}
add_action('admin_init', 'backtop_register_settings');
function backtop_position_field() {
$options = get_option('backtop_options');
?>
<select name="backtop_options[position]">
<option value="bottom-right" <?php selected($options['position'], 'bottom-right'); ?>>Bottom Right</option>
<option value="bottom-left" <?php selected($options['position'], 'bottom-left'); ?>>Bottom Left</option>
</select>
<?php
}
function backtop_color_field() {
$options = get_option('backtop_options');
?>
<input type="color" name="backtop_options[color]" value="<?php echo esc_attr($options['color']); ?>">
<?php
}
function backtop_size_field() {
$options = get_option('backtop_options');
?>
<input type="text" name="backtop_options[size]" value="<?php echo esc_attr($options['size']); ?>" placeholder="e.g., 40px">
<?php
}
function backtop_shape_field() {
$options = get_option('backtop_options');
?>
<select name="backtop_options[shape]">
<option value="circle" <?php selected($options['shape'], 'circle'); ?>>Circle</option>
<option value="square" <?php selected($options['shape'], 'square'); ?>>Square</option>
</select>
<?php
}
function backtop_margin_field() {
$options = get_option('backtop_options');
?>
<input type="text" name="backtop_options[margin]" value="<?php echo esc_attr($options['margin']); ?>" placeholder="e.g., 20px 20px">
<?php
}
function backtop_sanitize_options($options) {
$options['position'] = in_array($options['position'], ['bottom-right', 'bottom-left']) ? $options['position'] : 'bottom-right';
$options['color'] = sanitize_hex_color($options['color']);
$options['size'] = preg_match('/^\d+(px|em|%)$/', $options['size']) ? $options['size'] : '40px';
$options['shape'] = in_array($options['shape'], ['circle', 'square']) ? $options['shape'] : 'circle';
$options['margin'] = sanitize_text_field($options['margin']);
return $options;
}
JS:
(function () {
const backTopTool = document.getElementById("backtop-tool");
const backTopButton = document.getElementById("backtop");
const percentageDisplay = document.getElementById("backtop-percentage");
if (backTopTool && backTopButton) {
const { position, color, size, shape, margin } = backTopOptions || {};
const [vertical, horizontal] = position.split("-");
const [marginY, marginX] = margin.split(" ");
backTopTool.style[vertical] = marginY || "20px";
backTopTool.style[horizontal] = marginX || "20px";
backTopButton.style.backgroundColor = color || "#000";
backTopButton.style.width = size || "40px";
backTopButton.style.height = size || "40px";
backTopButton.style.borderRadius = shape === "circle" ? "50%" : "0";
percentageDisplay.style.fontSize = `${Math.max(parseInt(size) * 0.4, 8)}px`;
}
const updateScrollProgress = () => {
const scrollTop = window.scrollY;
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrollPercentage = Math.round((scrollTop / scrollHeight) * 100);
if (percentageDisplay) {
percentageDisplay.innerHTML = scrollPercentage >= 95 ? "▲" : `${scrollPercentage}%`;
}
if (backTopButton) {
backTopButton.classList.toggle("hidden", scrollTop < 200);
}
};
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
backTopButton?.addEventListener("click", scrollToTop);
window.addEventListener("scroll", updateScrollProgress);
})();
CSS:
#backtop-tool {
position: fixed;
z-index: 9999;
}
#backtop-tool ul {
list-style: none;
padding: 0;
margin: 0;
}
#backtop-tool .hidden {
display: none;
}
#backtop-tool li {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
width: var(--size, 40px);
height: var(--size, 40px);
background: var(--color, rgba(0, 0, 0, 0.7));
color: #fff;
border-radius: var(--shape, 50%);
cursor: pointer;
font-size: calc(var(--size, 40px) * 0.4); /* 根据按钮大小动态调整字体 */
text-align: center; /* 对齐数字文本 */
overflow: hidden;
box-sizing: border-box;
}
管理界面如下:

从插件制作到实现的过程,给我的感觉是通过插件对页面、功能进行修改,可以避免我们干扰WP核心文件,让页面中的其他功能不受影响,对灵活性、安全性都有一定的增强,这对于我这种半桶水来说非常利好,即便哪里设置错了直接把插件删了就好嘛,可玩性很高。
WordPress官网最近加上了一个飘雪效果,感觉效果相当不错,经过研究后发现他们页面中使用了两个js,而前端只需要用html进行简单调整就可以自定义,可玩性很强。

我们也可以在自己的网站中使用这个效果,首先在Wordpress官网下载到is-land及snow-fall这俩js。
通过模板函数对is-land与snow-fall先后加载:
// js来源自Wordpress官网
function enqueue_island_assets() {
wp_enqueue_script(
'island-js',
get_template_directory_uri() . '/island.js',
true
);
}
add_action('wp_enqueue_scripts', 'enqueue_island_assets');
wp_enqueue_script(
'snow-fall-js',
get_template_directory_uri() . '/snow-fall.js',
array('island-js'), // 设置依赖
true
);
前端通过html进行调用,我们通过后面的分析可以发现其为前端预留了多个参数调节,比如我想在页面呈现❄飘落,而不是原版的圆点,只需要调整text的值,写成如下即可:
<is-land on:media="(prefers-reduced-motion: no-preference)" on:idle="">
<snow-fall mode="page" text="❄"></snow-fall>
</is-land>
2025-1-6更新:如果不需要island里面的一些参数,那么直接引入snow-fall也是可以的:
// island及snowfall的Js文件来自Wordpress官网
function enqueue_snowfall() {
wp_enqueue_script(
'snow-fall-js',
get_template_directory_uri() . '/snow-fall.js',
true
);
}
add_action('wp_enqueue_scripts', 'enqueue_snowfall');
<snow-fall mode="page" text="❄"></snow-fall>
目前网站采用直接引入Snow-fall的方式展示。
后续是源码及一些可以调用的参数信息:
其中is-land的源码如下:
class Island extends HTMLElement {
static tagName = "is-land";
static prefix = "is-land--";
static attr = {
template: "data-island",
ready: "ready",
defer: "defer-hydration"
};
static onceCache = new Map;
static onReady = new Map;
static fallback = {
":not(is-land,:defined,[defer-hydration])": (e, t, a) => {
let n = document.createElement(a + t.localName);
for (let e of t.getAttributeNames())
n.setAttribute(e, t.getAttribute(e));
let i = t.shadowRoot;
if (!i) {
let e = t.querySelector(":scope > template:is([shadowrootmode], [shadowroot])");
if (e) {
let a = e.getAttribute("shadowrootmode") || e.getAttribute("shadowroot") || "closed";
i = t.attachShadow({
mode: a
}),
i.appendChild(e.content.cloneNode(!0))
}
}
return i && n.attachShadow({
mode: i.mode
}).append(...i.childNodes),
n.append(...t.childNodes),
t.replaceWith(n),
e.then(( () => {
n.shadowRoot && t.shadowRoot.append(...n.shadowRoot.childNodes),
t.append(...n.childNodes),
n.replaceWith(t)
}
))
}
};
constructor() {
super(),
this.ready = new Promise((e => {
this.readyResolve = e
}
))
}
static getParents(e, t=!1) {
let a = [];
for (; e; ) {
if (e.matches && e.matches(Island.tagName)) {
if (t && e === t)
break;
Conditions.hasConditions(e) && a.push(e)
}
e = e.parentNode
}
return a
}
static async ready(e, t) {
if (t || (t = Island.getParents(e)),
0 === t.length)
return;
let a = await Promise.all(t.map((e => e.wait())));
return a.length ? a[0] : void 0
}
forceFallback() {
window.Island && Object.assign(Island.fallback, window.Island.fallback);
for (let e in Island.fallback) {
let t = Array.from(this.querySelectorAll(e)).reverse();
for (let a of t) {
if (!a.isConnected)
continue;
let t = Island.getParents(a);
if (1 === t.length) {
let n = Island.ready(a, t);
Island.fallback[e](n, a, Island.prefix)
}
}
}
}
wait() {
return this.ready
}
async connectedCallback() {
Conditions.hasConditions(this) && this.forceFallback(),
await this.hydrate()
}
getTemplates() {
return this.querySelectorAll(`template[${Island.attr.template}]`)
}
replaceTemplates(e) {
for (let t of e) {
if (Island.getParents(t, this).length > 0)
continue;
let e = t.getAttribute(Island.attr.template);
if ("replace" === e) {
let e = Array.from(this.childNodes);
for (let t of e)
this.removeChild(t);
this.appendChild(t.content);
break
}
{
let a = t.innerHTML;
if ("once" === e && a) {
if (Island.onceCache.has(a))
return void t.remove();
Island.onceCache.set(a, !0)
}
t.replaceWith(t.content)
}
}
}
async hydrate() {
let e = [];
this.parentNode && e.push(Island.ready(this.parentNode));
let t = Conditions.getConditions(this);
for (let a in t)
Conditions.map[a] && e.push(Conditions.map[a](t[a], this));
await Promise.all(e),
this.replaceTemplates(this.getTemplates());
for (let e of Island.onReady.values())
await e.call(this, Island);
this.readyResolve(),
this.setAttribute(Island.attr.ready, ""),
this.querySelectorAll(`[${Island.attr.defer}]`).forEach((e => e.removeAttribute(Island.attr.defer)))
}
}
class Conditions {
static map = {
visible: Conditions.visible,
idle: Conditions.idle,
interaction: Conditions.interaction,
media: Conditions.media,
"save-data": Conditions.saveData
};
static hasConditions(e) {
return Object.keys(Conditions.getConditions(e)).length > 0
}
static getConditions(e) {
let t = {};
for (let a of Object.keys(Conditions.map))
e.hasAttribute(`on:${a}`) && (t[a] = e.getAttribute(`on:${a}`));
return t
}
static visible(e, t) {
if ("IntersectionObserver"in window)
return new Promise((e => {
let a = new IntersectionObserver((t => {
let[n] = t;
n.isIntersecting && (a.unobserve(n.target),
e())
}
));
a.observe(t)
}
))
}
static idle() {
let e = new Promise((e => {
"complete" !== document.readyState ? window.addEventListener("load", ( () => e()), {
once: !0
}) : e()
}
));
return "requestIdleCallback"in window ? Promise.all([new Promise((e => {
requestIdleCallback(( () => {
e()
}
))
}
)), e]) : e
}
static interaction(e, t) {
let a = ["click", "touchstart"];
return e && (a = (e || "").split(",").map((e => e.trim()))),
new Promise((e => {
function n(i) {
e();
for (let e of a)
t.removeEventListener(e, n)
}
for (let e of a)
t.addEventListener(e, n, {
once: !0
})
}
))
}
static media(e) {
let t = {
matches: !0
};
if (e && "matchMedia"in window && (t = window.matchMedia(e)),
!t.matches)
return new Promise((e => {
t.addListener((t => {
t.matches && e()
}
))
}
))
}
static saveData(e) {
if ("connection"in navigator && navigator.connection.saveData !== ("false" !== e))
return new Promise(( () => {}
))
}
}
"customElements"in window && (window.customElements.define(Island.tagName, Island),
window.Island = Island);
export {Island, Island as component};
export const ready = Island.ready;
通过前端代码调用:
<is-land on:media="(prefers-reduced-motion: no-preference)" on:idle="" ready="">
<snow-fall mode="page"></snow-fall>
</is-land>
其中可以定义:
on:media:当满足媒体查询条件时加载内容。
on:idle:在浏览器空闲时间或页面完全加载后执行。
on:visible:当标签内容滚动到视口中时加载。
on:interaction:在用户交互(如点击、触摸)后加载。
on:save-data:根据设备的省流量模式决定是否加载内容。
对于snow-fall.js,源码如下:
class Snow extends HTMLElement {
static random(t, e) {
return t + Math.floor(Math.random() * (e - t) + 1)
}
static attrs = {
count: "count",
mode: "mode",
text: "text"
};
generateCss(t, e) {
let n = [];
n.push('\n:host([mode="element"]) {\n\tdisplay: block;\n\tposition: relative;\n\toverflow: hidden;\n}\n:host([mode="page"]) {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n}\n:host([mode="page"]),\n:host([mode="element"]) > * {\n\tpointer-events: none;\n}\n:host([mode="element"]) ::slotted(*) {\n\tpointer-events: all;\n}\n* {\n\tposition: absolute;\n}\n:host([text]) * {\n\tfont-size: var(--snow-fall-size, 1em);\n}\n:host(:not([text])) * {\n\twidth: var(--snow-fall-size, 10px);\n\theight: var(--snow-fall-size, 10px);\n\tbackground: var(--snow-fall-color, rgba(255,255,255,.5));\n\tborder-radius: 50%;\n}\n');
let o = {
width: 100,
height: 100
}
, a = {
x: "vw",
y: "vh"
};
"element" === t && (o = {
width: this.firstElementChild.clientWidth,
height: this.firstElementChild.clientHeight
},
a = {
x: "px",
y: "px"
});
for (let t = 1; t <= e; t++) {
let e = Snow.random(1, 100) * o.width / 100
, s = Snow.random(-10, 10) * o.width / 100
, i = Math.round(Snow.random(30, 100))
, l = i * o.height / 100
, r = o.height
, h = 1e-4 * Snow.random(1, 1e4)
, d = Snow.random(10, 30)
, m = -1 * Snow.random(0, 30);
n.push(`\n:nth-child(${t}) {\n\topacity: ${.001 * Snow.random(0, 1e3)};\n\ttransform: translate(${e}${a.x}, -10px) scale(${h});\n\tanimation: fall-${t} ${d}s ${m}s linear infinite;\n}\n\n@keyframes fall-${t} {\n\t${i}% {\n\t\ttransform: translate(${e + s}${a.x}, ${l}${a.y}) scale(${h});\n\t}\n\n\tto {\n\t\ttransform: translate(${e + s / 2}${a.x}, ${r}${a.y}) scale(${h});\n\t}\n}`)
}
return n.join("\n")
}
connectedCallback() {
if (this.shadowRoot || !("replaceSync"in CSSStyleSheet.prototype))
return;
let t, e = parseInt(this.getAttribute(Snow.attrs.count)) || 100;
this.hasAttribute(Snow.attrs.mode) ? t = this.getAttribute(Snow.attrs.mode) : (t = this.firstElementChild ? "element" : "page",
this.setAttribute(Snow.attrs.mode, t));
let n = new CSSStyleSheet;
n.replaceSync(this.generateCss(t, e));
let o = this.attachShadow({
mode: "open"
});
o.adoptedStyleSheets = [n];
let a = document.createElement("div")
, s = this.getAttribute(Snow.attrs.text);
a.innerText = s || "";
for (let t = 0, n = e; t < n; t++)
o.appendChild(a.cloneNode(!0));
o.appendChild(document.createElement("slot"))
}
}
customElements.define("snow-fall", Snow);
其定义了一个自定义 HTML 元素 <snow-fall>,用于实现落雪效果。
我们可以通过改变js中的一些数值来变更效果,例如
static attrs = { count: “count”, mode: “mode”, text: “text” };
count:雪花的数量,默认值是 100。
mode:两种模式:
1、element:作用于某个特定元素。
2、page:覆盖整个页面。
text:雪花的文本内容(默认为空,显示为圆形点)。
page模式目前已经满足使用,如果想使用element模式目前似乎会出现firstElementChild 为空或未能正确获取其 clientWidth,导致 generateCss 方法出错。如何正确使用element模式还需要探究。
通过 generateCss(t, e) { 动态生成CSS,可以定义:
1、:host([mode=”element”]) 和 :host([mode=”page”]) 用于适配不同模式。
2、雪花的大小由 –snow-fall-size 决定,颜色由 –snow-fall-color 决定。
动态生成每个雪花的动画样式:
1、起始位置:translate(${e}${a.x}, -10px)
2、随机透明度:opacity
3、动画时长:animation: fall-${t} ${d}s ${m}s linear infinite;
4、动画帧 @keyframes: 中间位置:随机 x 偏移,随机 y 偏移。最终位置:完全落到底部。
最近坏了一颗12-40 F2.8二代港版,使用过程中发送碰撞,镜头卡口与镜身固定螺柱发生断裂,只能送修换部件。考虑到奥之心在国内可怜的占有率,因此选择官方售后进行维修,体验了一次奥之心在国内的送修服务,整体流程倒是非常顺畅,在此做简单分享。
奥之心如今的官方维修网络分为三个层级:客户服务中心、微型单电维修中心以及微型单电受理中心。其中客户服务中心为官方设立,微型单电维修中心为授权,应该可以简单理解为有资质的第三方,受理中心则会将受理的设备转送到客户服务中心或者维修中心进行修复。
本次维修选择的是上海客户服务中心,由于其只提供寄修服务,因此在寄送之前需要做好沟通工作。
直接拨打021-61767030转分机8016,咨询工程师。
寄送时候,工程师需要你在其中放一个纸条,写上一些基本信息,主要是联系方式及所要修理的问题,例如——
联系方式:
姓名:XX
电话:XXXXX
回寄地址:XXXXXXX
存在的问题:(以下是我当时写的问题)
- 拍摄时候发现成像一侧不清晰;
- 初步检查发现底部卡口与镜身连接有旷量;
- 自行拆解后进一步检查发现两个螺丝柱断裂;
- 自行拆解后,螺丝垫片掉落,因此目前顺序与数量不确定是否出错;
- 安装回时扭矩过大,镜头卡口两颗螺丝有滑丝情况。
寄送到上海客户服务中心,邮费需要自理。
然后中间可能需要等待3-5个工作日的时间,等待工程师联系你,我当时是等了大概5天,期间可以打上面的021开头电话询问工程师进度,不过一般工程师检测后都会联系你。
工程师检测后告知镜头存在的问题及需要的维修方式,更换的部件等,然后会给出大致价格。
而后,奥之心会给你发送一个短信,告知具体的维修价格并附上汇款账户,而后付款。

本次维修时间较快,付款维修后当天傍晚就顺丰发回,注意同样是到付,奥之心不会承担邮费。
本次维修中寄回的内容包括修复完成的镜头、损坏的部件、奥之心维修清单,其中清单记载物料费用439,人工费用500。
如果要开票,可以联系工程师,对方会短信发送邮箱,你可以把开票信息发送到邮箱,等待月底后拿到发票。
奥之心虽然作为Other中的Other,但是维修网络依旧在正常运转,而且体验尚可,维修手艺也是不错,作为M43难兄难弟之一,在中国市场的蜜汁定价下还有可靠的售后也属实不易,奥之心真的可以重新考虑以下自己在中国市场的定位和定价,或许还能在Other里绽放一些光芒。
Adobe Dimension在三维软件中似乎是一个另类,纵观全网也没有什么热度与讨论,而随着其推出4.1更新,我再次怀着好奇心试了一试该软件,发觉其可以成为一个快速建模与出图的高效三维软件——起码我现在能打开它了。
(更新:现在还是会出现无论如何都没法稳定打开的情况,算了,还是用Blender吧)

作为正版软件受害者,在使用数次再打开它之时,熟悉的提示又一次出现,NND怎么还是躲不了。

经过一系列有的没的操作,我(有概率)解决Dimension的警告问题。
操作如下,前三步一定先完成。
2、在如下窗口结束所有会话,该操作会让所有已登录设备、产品掉线,谨慎操作。

3、重新在电脑登录Adobe Creative Cloud。
4、进入Dimension检查是否有警告提示,一般这个时候已经可以解决警告提示的问题,如果还是有问题,那么看下面↓
5、(这部分是俺寻思环节,并不能稳定复现)如果再次出现警告提示,可以先检查Adobe相关进程。在正常情况下,Adobe Creative Cloud的后台进程一般会有四五个的样子,类似下图:

而每次Dimension出现问题的时候基本都是Adobe相关网络进程停止导致的,例如下图这种情况时后台进程基本是完全停止的。

因此猜测正常状态下的Adobe Creative Cloud中有激活服务器之类的相关进程才能保证Dimension不会出现警告提示,而且这些进程似乎只和Dimension有关,套件中的其他软件都不受此影响,但有些软件例如PS之类的又似乎可以调用这些进程。

根据测试,可能是和地址与127.0.0.1相关的网络进程有关。因此如果在结束会话步骤后依旧时不时出现问题,可以试试以下的方法:
解决方法:【不能稳定复现,可以排列组合做几次】
1、若遇到Adobe Creative Cloud服务异常,可以等待操作台启动完毕,或者直接重启Adobe服务,等待与127.0.0.1相关的网络进程完成网络监听后打开Dimension,测试是否恢复正常。
2、若遇到开机第一次可以使用但是第二次又提示错误,但Cloud服务控制台正常加载,可以在网络进程中查看127.0.0.1上的Adobe进程,先关闭相关进程后尝试打开软件,有概率恢复正常。
若依旧错误则可以点击Cloud控制台中的检查更新按钮,可以重启网络进程,等待相关网络进程完成网络连接或监听,再打开Dimension,有概率恢复正常。
以上,验证过程过于折磨且很难稳定复现,不过步骤123请一定要做。
至于做了结束会话之后还遇到问题,经过一定的测试大概率是127.0.0.1上网络进程有关,核心思路还是关闭或重启Adobe在127.0.0.1相关端口的网络进程或者让Adobe其他软件调用端口,直到Dimension可以正常启动,若后续有可以稳定Dimension的方法再更新本文。(本文终结,不会再折腾Dimension,都不能稳定打开的软件真是太折磨人了。)