前言
ios用户当更新到iOS14后,咱们的iPhone等ios设施反对咱们用户自定义桌面小物件(又或者称之为小组件、桌面挂件),利用这个个性,网上呈现了许许多多诸如通明时钟、微博热搜、知乎热榜、网易云热评、特斯拉、BMW、名爵、奥迪等等的iPhone桌面,看如下理论效果图:
那这到底是怎么实现的,咱们怎么能力制作一款本人的iPhone共性桌面?明天给大家分享的就是Scriptable的桌面玩法,对于javascript开发人员来说,看完这篇教程,上手小物件开发利用是信手拈来的事儿,而对于没有编程根底的同学不必放心看不懂,你所要做的就是复制粘贴,间接跳过开发教程,看文章开端快速通道即可。
Scriptable介绍
这是一款可让您应用 JavaScript 自动化构建 iOS 的应用程序
以上是对Scriptable的官网解释,这对前端开发者来说无疑是一个福音,因为Scriptable 应用 Apple 的JavaScriptCore,它默认就反对ECMAScript 6对小组件进行开发构建。
如果您刚刚开始应用 JavaScript,您可能想看看 Codecademys Intro to Programming in JavaScript。无关 JavaScript 性能的疾速参考,您能够参考 W3Schools 的JavaScript 教程。
请留神,一些指南和教程会假如您在浏览器中运行 JavaScript,因而能够拜访特定于浏览器的对象,例如文档。Scriptable 不在浏览器中运行 JavaScript,因而不存在此类对象。
更多对于Scriptable的解释请浏览官网文档
要害个性
先看一张图:
下面列举的是一些Scriptable的个性,这些个性包含:
- 反对ES6语法
- 能够应用JavaScript调用一些原生的API
- Siri 快捷方式
- 欠缺的文档反对
- 共享表格扩大
- 文件系统继承
- 编辑器的自定义
- 代码样例
- 以及通过x-callback-url和其它APP交互
是不是感觉反对的个性还是挺多的,这些个性曾经足够让咱们去实现很多原生级底层的交互了。
第一个小物件程序
// 判断是否是运行在桌面的组件中if (config.runsInWidget) { // 创立一个显示元素列表的小部件 // 显示元素列表的小部件。将小部件传递给 Script.setWidget() 将其显示在您的主屏幕上。 // 请留神,小部件会定期刷新,小部件刷新的速率很大水平上取决于操作系统。 // 另请留神,在小部件中运行脚本时存在内存限度。当应用太多内存时,小部件将解体并且无奈正确出现。 const widget = new ListWidget(); // 增加文本物件 const text = widget.addText("Hello, World!"); // 设置字体色彩 text.textColor = new Color("#000000"); // 设置字体大小 text.font = Font.boldSystemFont(36); // 设置文字对齐形式 text.centerAlignText(); // 新建线性突变物件 const gradient = new LinearGradient(); // 每种色彩的地位,每个地位应该是 0 到 1 范畴内的值,并批示突变colors数组中每种色彩的地位 gradient.locations = [0, 1]; // 突变的色彩。locations色彩数组应蕴含与突变属性雷同数量的元素。 gradient.colors = [new Color("#F5DB1A"), new Color("#F3B626")]; // 把设置好的渐变色配置给显示元素列表的小部件背景 widget.backgroundGradient = gradient; // 设置部件 Script.setWidget(widget);}
通过以上简略的显示"Hello, World!"并设置背景色和文字款式的程序来看,有一个重要的概念须要javascript程序员去了解和从传统的web开发的概念中转换过去,如果你之前有开发过Flutter
开发教训的话,那么对你来说,开发Scriptable利用应该是有共鸣的。因为对于我看来,Scriptable同样也是万物皆组件(widget)的概念,撑持这一点的一个重要思维就是面向对象。
万物皆组件
何为万物皆组件?无论是容器(div)还是款式(color、style)还是元素(font)等等全是Object,比方你要显示一行文字"Hello, World!",那么你首先必须要有一个容器(div)去装载这行文字(fonts),你还要去给文字设置款式(styles),那样式也不是说凭空生成,但凡对象,都要new进去。对照以上"Hello, World!"的例子再深刻了解这个概念。
以上概念对Scriptable利用开发有极其重要的踊跃作用,尤其是对于高级前端开发者或没有原生app开发教训的开发者来说,他们很难脱离传统web这种mvvc或者mvc的开发模式去思考面向对象的开发模式。
高频罕用的组件
ListWidge
显示元素列表的小部件,最罕用的容器组件。个别组件利用的根元素都用ListWidget包裹,也只有用这个组件能力传递给 Script.setWidget() 将其显示在您的主屏幕上。
请留神,小部件会定期刷新,并且小部件刷新的速率很大水平上取决于操作系统。留神:利用这一点能够做很多须要基于定时刷新的利用,比方:节日纪念日,须要计算以后工夫的利用。
另请留神,在小部件中运行脚本时存在内存限度。当应用太多内存时,小部件将解体并且无奈正确出现。
-addStack
addStack(): WidgetStack
增加堆栈。
ListWidget.addStack()
返回值是WidgetStack
(堆栈元素),将堆栈元素增加到ListWidget中是程度布局的,能够利用这个api
实现相似于flex
布局
-addSpacer
addSpacer(length: number): WidgetSpacer
向小部件增加距离。这可用于在小部件中垂直偏移内容。相似于web开发中css的margin
-setPadding
setPadding(top: number, leading: number, bottom: number, trailing: number)
设置小部件每一侧的填充。相似web中css的padding
-addText
addText(text: string): WidgetText
将文本元素增加到小部件。应用返回元素的属性来设置文本款式。类比web开发中的向div中插入文本节点。
backgroundColor
backgroundColor: Color
设置容器的背景色彩,值必须是Color类型(new Color('#fff', 1)
),Color构造函数的第一个参数为色值,第二个参数为透明度,相似web开发中的rgba(255,255,255,1)
backgroundImage
backgroundImage: Image
设置容器的背景图片。相似web中css的backgroud-image
Font
示意字体和文本大小。
new Font(name: string, size: number)
该字体可用于设置文本款式,例如在小部件中。
- regularSystemFont
创立惯例零碎字体。
static regularSystemFont(size: number): Font
-lightSystemFont
创立白天模式零碎字体。
static lightSystemFont(size: number): Font
-thinSystemFont
创立细零碎字体。
static thinSystemFont(size: number): Font
Keychain
钥匙串是凭据、密钥等的平安存储。应用该set()
办法将值增加到钥匙串。而后,您能够稍后应用该get()
办法检索该值。
-contains
查看钥匙串是否蕴含钥匙。
static contains(key: string): bool
查看钥匙串是否蕴含指定的钥匙。
-set
将指定键的值增加到钥匙串。
static set(key: string, value: string)
将值增加到钥匙串,将其调配给指定的键。如果密钥已存在于钥匙串中,则该值将被笼罩。
值平安地存储在加密数据库中。
-get
从钥匙串中读取一个值。
static get(key: string): string
读取指定键的值。如果密钥不存在,该办法将引发谬误。应用该contains
办法查看钥匙串中是否存在钥匙。
Alert
显示模态弹窗。相似web ui中的Modal组件
应用它来配置以模态或表单模式出现的弹窗。配置弹窗后,调用 presentAlert() 或 presentSheet() 以出现弹窗。这两种示意办法将返回一个值,该值携带实现时抉择的操作的索引。比方你弹窗增加了两个操作按钮,先增加一个是确定
,另一个是勾销
按钮,增加操作跟js中的数组统一,先增加的按钮索引就是 0,当用户点击确认
按钮的时候,alert.presentAlert()
返回的值就是'确认'在配置数组中的索引值,即为0。
集体认为这个组件也是十分高频的组件,因为在高级桌面组件或者简单的组件,尤其是一些须要用户登录账号信息的桌面组件来说,须要弹窗让用户输出账号密码等交互行为,又或者让用户输出日期、名称等须要长久化存储的场景,Alert组件是不二之选。
-message
title: string
弹窗中显示的题目。通常是一个短字符串。
-addAction
向弹窗中增加操作按钮。要查看是否抉择了某个操作,您应该应用在 presentAlert() 和 presentSheet() 返回的Promise
时提供的第一个参数。
// 创立一个弹窗组件let alert = new Alert();// 设置弹窗中显示的contentalert.message = '弹窗中显示的内容,这里能够展现对操作的解释等文案信息...';// 向弹窗中退出一个按钮-确定,索引为0alert.addAction('确定');// 向弹窗中退出一个按钮-勾销,所以为1alert.addAction('勾销');// 获取弹窗按钮被触发后拿到用户点击的具体某个按钮索引,如果点击确定,response === 0 否则 response === 1let response = await alert.presentAlert();
-addCancelAction
addCancelAction(title: string)
向弹窗中增加勾销操作。抉择勾销操作时,kidealert()或vistentheet()提供的索引将始终为-1。请留神,在 iPad 上运行并应用 presentSheet() 进行演示时,该操作不会显示在操作列表中。通过在工作表外点击可勾销操作。
弹窗只能蕴含一个勾销操作。尝试增加更多勾销操作将删除之前增加的任何勾销操作。
-presentAlert
显示模态弹出窗,相似elementui
中modal
的visible
设置为true
,此时弹窗显示。
-presentSheet
将弹窗以相似bottomSheet交互方式弹出。
Image
治理图像数据。
图像对象蕴含图像数据。Scriptable 中解决图像的 API(通过将图像作为输出或返回图像)将应用此 Image 类型。
-size
size: Size
图像的大小(以像素为单位)。只读
-fromFile
从指定的文件门路加载图像。如果无奈读取图像,该函数将返回 null。相似web开发中读取本地(ios中还有iCloud)图片文件
-fromData
static fromData(data: Data): Image
从原始数据加载图像。如果无奈读取图像,该函数将返回 null。
Data能够是字符串、文件和图像的原始数据示意。例如,Image中用的比拟多的就是从base64字符串中读取图片,伪代码示例如下:
let imageDataString = 'base64:xxxxx'let imageData = Data.fromBase64String(imageDataString)// Convert to image and crop before returning.let imageFromData = Image.fromData(imageData)// return Image(imageFromData)return imageFromData
更多对于Data的其余api请参考文档
Photos
提供对您的照片库的拜访。
为了从您的照片库中读取,您必须授予应用程序拜访您的照片库的权限。首次应用 API 时,利用会提醒拜访,但如果您拒绝请求,所有 API 调用都会失败。在这种状况下,您必须从零碎设置中启用对照片库的拜访。
这个api用的也是绝对高频的一个,因为大部分场景下,你的widget都须要用到图片或者背景,而应用图片的大部分场景(特地是背景图)都须要拜访你的设施图库,也就是你的相册,当然应用相册性能必须在用户受权的前提下。
-fromLibrary
static fromLibrary(): Promise<Image>
显示用于抉择图像的照片库,应用它从照片库中筛选图像。
应用它:
const img = await Photos.fromLibrary();// 拿到Image对象后,能够对它做缓存、展现、传输等等用处
-latestPhoto
获取最新照片。
static latestPhoto(): Promise<Image>
从您的照片库中读取最新照片。如果没有可用的照片,则承诺将被回绝。
-latestScreenshot
获取最新截图。
static latestScreenshot(): Promise<Image>
从您的照片库中读取最新的屏幕截图。如果没有可用的屏幕截图,则 Promise 将被回绝。
Pasteboard
复制并粘贴字符串或图像。
从粘贴板复制和粘贴字符串和图像。
-copy
将字符串复制到粘贴板。
static copy(string: string)
-paste
从粘贴板粘贴字符串。
static paste(): string
-copyImage
将图像复制到粘贴板。
static copyImage(image: Image)
LinearGradient
线性突变。
要在小部件中应用的线性突变。
-colors
突变的色彩。
locations
色彩数组应蕴含与突变属性雷同数量的元素。
colors: [Color]
相似css中linear-gradient
属性的第二、三个从参数,示意突变的色彩范畴
.horizontal-gradient { background: linear-gradient(to right, blue, pink);}
-locations
每种色彩的地位。
每个地位应该是 0 到 1 范畴内的值,并批示突变colors
数组中每种色彩的地位。
colors
地位数组应蕴含与突变属性雷同数量的元素。
locations: [number]
const bg = new LinearGradient()bg.locations = [0, 1]bg.colors = [ new Color('#f35942', 1), new Color('#e92d1d', 1)]w.backgroundGradient = bg
FileManager
此api实用于做缓存数据用,比拟罕用的api之一,应用频次较高
-local
创立一个本地 FileManager。
static local(): FileManager
创立一个文件管理器,用于操作本地存储的文件。
const files = FileManager.local();
-iCloud
创立一个 iCloud 文件管理器。
static iCloud(): FileManager
创立一个文件管理器,用于操作存储在 iCloud 中的文件。必须在设施上启用 iCloud 能力应用它。
-read
将文件的内容作为数据读取。
read(filePath: string): Data
读取文件门路指定的文件内容作为原始数据。要将文件作为字符串readString(filePath)
读取,请参见并将其作为图像读取,请参见readImage(filePath)
.
如果文件不存在或存在于 iCloud 但尚未下载,该函数将出错。用于fileExists(filePath)
查看文件是否存在并downloadFileFromiCloud(filePath)
下载文件。请留神,调用 始终是平安的downloadFileFromiCloud(filePath)
,即便文件本地存储在设施上。
-readImage
将文件的内容作为图像读取。
readImage(filePath: string): Image
读取文件门路指定的文件内容并将其转换为图像。
// 读取本人在本地缓存的图片const img = files.readImage(files.joinPath(files.documentsDirectory(), "avatar.jpg"))
-write
将数据写入文件。
write(filePath: string, content: Data)
-writeImage
将图像写入文件。
writeImage(filePath: string, image: Image)
将图像写入磁盘上的指定文件门路。如果该文件尚不存在,则会创立该文件。如果文件曾经存在,则文件的内容将被新内容笼罩。
-fileExists
查看文件是否存在。
fileExists(filePath: string): bool
查看文件是否存在于指定的文件门路中。在挪动或复制到指标之前查看这一点可能是一个好主见,因为这些操作将替换指标文件门路中的任何现有文件。
-documentsDirectory
文档目录的门路。
documentsDirectory(): string
用于检索文档目录的门路。您的脚本存储在此目录中。如果您启用了 iCloud,您的脚本将存储在 iCloud 的文档目录中,否则它们将存储在本地文档目录中。该目录可用于长期存储。能够应用“文件”应用程序拜访存储在此目录中的文档。存储在本地文档目录中的文件不会呈现在“文件”应用程序中。
-joinPath
连贯两个门路组件。性能同node
中的joinPath
joinPath(lhsPath: string, rhsPath: string): string
连贯两条门路以创立一条门路。例如,用文件名连贯到目录的门路。这是创立传递给 FileManager 的读取和写入函数的新文件门路的倡议办法。
封装罕用办法
网络申请
/** * HTTP 申请接口 * @param {string} url 申请的url * @param {bool} json 返回数据是否为 json,默认 true * @param {bool} useCache 是否采纳离线缓存(申请失败后获取上一次后果), * @return {string | json | null}*/async httpGet(url, json = true, useCache = false) { let data = null const cacheKey = this.md5(url) if (useCache && Keychain.contains(cacheKey)) { let cache = Keychain.get(cacheKey) return json ? JSON.parse(cache) : cache } try { let req = new Request(url) data = await (json ? req.loadJSON() : req.loadString()) } catch (e) {} // 判断数据是否为空(加载失败) if (!data && Keychain.contains(cacheKey)) { // 判断是否有缓存 let cache = Keychain.get(cacheKey) return json ? JSON.parse(cache) : cache } // 存储缓存 Keychain.set(cacheKey, json ? JSON.stringify(data) : data) return data}
获取近程图片
/** * 获取近程图片内容 * @param {string} url 图片地址 * @param {bool} useCache 是否应用缓存(申请失败时获取本地缓存)*/async getImageByUrl(url, useCache = true) { const cacheKey = this.md5(url) const cacheFile = FileManager.local().joinPath(FileManager.local().temporaryDirectory(), cacheKey) // 判断是否有缓存 if (useCache && FileManager.local().fileExists(cacheFile)) { return Image.fromFile(cacheFile) } try { const req = new Request(url) const img = await req.loadImage() // 存储到缓存 FileManager.local().writeImage(cacheFile, img) return img } catch (e) { // 没有缓存+失败状况下,返回自定义的绘制图片(红色背景) throw new Error('加载图片失败'); }}
带透明度的背景图
async function shadowImage(img) { let ctx = new DrawContext() // 把画布的尺寸设置成图片的尺寸 ctx.size = img.size // 把图片绘制到画布中 ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height'])) // 设置绘制的图层色彩,为半透明的彩色 ctx.setFillColor(new Color('#000000', 0.5)) // 绘制图层 ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height'])) // 导出最终图片 return await ctx.getImage()}
获取时间差
function getDistanceSpecifiedTime(dateTime) { // 指定日期和工夫 var EndTime = new Date(dateTime); // 以后零碎工夫 var NowTime = new Date(); var t = EndTime.getTime() - NowTime.getTime(); var d = Math.floor(t / 1000 / 60 / 60 / 24); var h = Math.floor(t / 1000 / 60 / 60 % 24); var m = Math.floor(t / 1000 / 60 % 60); var s = Math.floor(t / 1000 % 60); return d;}
所有反对的手机小物件像素大小和地位
罕用来设置伪通明背景
// Pixel sizes and positions for widgets on all supported phones.function phoneSizes() { let phones = { // 12 and 12 Pro "2532": { small: 474, medium: 1014, large: 1062, left: 78, right: 618, top: 231, middle: 819, bottom: 1407 }, // 11 Pro Max, XS Max "2688": { small: 507, medium: 1080, large: 1137, left: 81, right: 654, top: 228, middle: 858, bottom: 1488 }, // 11, XR "1792": { small: 338, medium: 720, large: 758, left: 54, right: 436, top: 160, middle: 580, bottom: 1000 }, // 11 Pro, XS, X "2436": { small: 465, medium: 987, large: 1035, left: 69, right: 591, top: 213, middle: 783, bottom: 1353 }, // Plus phones "2208": { small: 471, medium: 1044, large: 1071, left: 99, right: 672, top: 114, middle: 696, bottom: 1278 }, // SE2 and 6/6S/7/8 "1334": { small: 296, medium: 642, large: 648, left: 54, right: 400, top: 60, middle: 412, bottom: 764 }, // SE1 "1136": { small: 282, medium: 584, large: 622, left: 30, right: 332, top: 59, middle: 399, bottom: 399 }, // 11 and XR in Display Zoom mode "1624": { small: 310, medium: 658, large: 690, left: 46, right: 394, top: 142, middle: 522, bottom: 902 }, // Plus in Display Zoom mode "2001" : { small: 444, medium: 963, large: 972, left: 81, right: 600, top: 90, middle: 618, bottom: 1146 } } return phones}
获取截图中的组件剪裁图
/** * 获取截图中的组件剪裁图 * 可用作通明背景 * 返回图片image对象 * 代码改自:https://gist.github.com/mzeryck/3a97ccd1e059b3afa3c6666d27a496c9 * @param {string} title 开始解决前提醒用户截图的信息,可选(适宜用在组件自定义通明背景时提醒)*/async getWidgetScreenShot (title = null) { // Generate an alert with the provided array of options. async function generateAlert(message,options) { let alert = new Alert() alert.message = message for (const option of options) { alert.addAction(option) } let response = await alert.presentAlert() return response } // Crop an image into the specified rect. function cropImage(img,rect) { let draw = new DrawContext() draw.size = new Size(rect.width, rect.height) draw.drawImageAtPoint(img,new Point(-rect.x, -rect.y)) return draw.getImage() } async function blurImage(img,style) { const blur = 150 const js = `var mul_table=[512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];var shg_table=[9,11,12,13,13,14,14,15,15,15,15,16,16,16,16,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24];function stackBlurCanvasRGB(id,top_x,top_y,width,height,radius){if(isNaN(radius)||radius<1)return;radius|=0;var canvas=document.getElementById(id);var context=canvas.getContext("2d");var imageData;try{try{imageData=context.getImageData(top_x,top_y,width,height)}catch(e){try{netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");imageData=context.getImageData(top_x,top_y,width,height)}catch(e){alert("Cannot access local image");throw new Error("unable to access local image data: "+e);return}}}catch(e){alert("Cannot access image");throw new Error("unable to access image data: "+e);}var pixels=imageData.data;var x,y,i,p,yp,yi,yw,r_sum,g_sum,b_sum,r_out_sum,g_out_sum,b_out_sum,r_in_sum,g_in_sum,b_in_sum,pr,pg,pb,rbs;var div=radius+radius+1;var w4=width<<2;var widthMinus1=width-1;var heightMinus1=height-1;var radiusPlus1=radius+1;var sumFactor=radiusPlus1*(radiusPlus1+1)/2;var stackStart=new BlurStack();var stack=stackStart;for(i=1;i<div;i++){stack=stack.next=new BlurStack();if(i==radiusPlus1)var stackEnd=stack}stack.next=stackStart;var stackIn=null;var stackOut=null;yw=yi=0;var mul_sum=mul_table[radius];var shg_sum=shg_table[radius];for(y=0;y<height;y++){r_in_sum=g_in_sum=b_in_sum=r_sum=g_sum=b_sum=0;r_out_sum=radiusPlus1*(pr=pixels[yi]);g_out_sum=radiusPlus1*(pg=pixels[yi+1]);b_out_sum=radiusPlus1*(pb=pixels[yi+2]);r_sum+=sumFactor*pr;g_sum+=sumFactor*pg;b_sum+=sumFactor*pb;stack=stackStart;for(i=0;i<radiusPlus1;i++){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next}for(i=1;i<radiusPlus1;i++){p=yi+((widthMinus1<i?widthMinus1:i)<<2);r_sum+=(stack.r=(pr=pixels[p]))*(rbs=radiusPlus1-i);g_sum+=(stack.g=(pg=pixels[p+1]))*rbs;b_sum+=(stack.b=(pb=pixels[p+2]))*rbs;r_in_sum+=pr;g_in_sum+=pg;b_in_sum+=pb;stack=stack.next}stackIn=stackStart;stackOut=stackEnd;for(x=0;x<width;x++){pixels[yi]=(r_sum*mul_sum)>>shg_sum;pixels[yi+1]=(g_sum*mul_sum)>>shg_sum;pixels[yi+2]=(b_sum*mul_sum)>>shg_sum;r_sum-=r_out_sum;g_sum-=g_out_sum;b_sum-=b_out_sum;r_out_sum-=stackIn.r;g_out_sum-=stackIn.g;b_out_sum-=stackIn.b;p=(yw+((p=x+radius+1)<widthMinus1?p:widthMinus1))<<2;r_in_sum+=(stackIn.r=pixels[p]);g_in_sum+=(stackIn.g=pixels[p+1]);b_in_sum+=(stackIn.b=pixels[p+2]);r_sum+=r_in_sum;g_sum+=g_in_sum;b_sum+=b_in_sum;stackIn=stackIn.next;r_out_sum+=(pr=stackOut.r);g_out_sum+=(pg=stackOut.g);b_out_sum+=(pb=stackOut.b);r_in_sum-=pr;g_in_sum-=pg;b_in_sum-=pb;stackOut=stackOut.next;yi+=4}yw+=width}for(x=0;x<width;x++){g_in_sum=b_in_sum=r_in_sum=g_sum=b_sum=r_sum=0;yi=x<<2;r_out_sum=radiusPlus1*(pr=pixels[yi]);g_out_sum=radiusPlus1*(pg=pixels[yi+1]);b_out_sum=radiusPlus1*(pb=pixels[yi+2]);r_sum+=sumFactor*pr;g_sum+=sumFactor*pg;b_sum+=sumFactor*pb;stack=stackStart;for(i=0;i<radiusPlus1;i++){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next}yp=width;for(i=1;i<=radius;i++){yi=(yp+x)<<2;r_sum+=(stack.r=(pr=pixels[yi]))*(rbs=radiusPlus1-i);g_sum+=(stack.g=(pg=pixels[yi+1]))*rbs;b_sum+=(stack.b=(pb=pixels[yi+2]))*rbs;r_in_sum+=pr;g_in_sum+=pg;b_in_sum+=pb;stack=stack.next;if(i<heightMinus1){yp+=width}}yi=x;stackIn=stackStart;stackOut=stackEnd;for(y=0;y<height;y++){p=yi<<2;pixels[p]=(r_sum*mul_sum)>>shg_sum;pixels[p+1]=(g_sum*mul_sum)>>shg_sum;pixels[p+2]=(b_sum*mul_sum)>>shg_sum;r_sum-=r_out_sum;g_sum-=g_out_sum;b_sum-=b_out_sum;r_out_sum-=stackIn.r;g_out_sum-=stackIn.g;b_out_sum-=stackIn.b;p=(x+(((p=y+radiusPlus1)<heightMinus1?p:heightMinus1)*width))<<2;r_sum+=(r_in_sum+=(stackIn.r=pixels[p]));g_sum+=(g_in_sum+=(stackIn.g=pixels[p+1]));b_sum+=(b_in_sum+=(stackIn.b=pixels[p+2]));stackIn=stackIn.next;r_out_sum+=(pr=stackOut.r);g_out_sum+=(pg=stackOut.g);b_out_sum+=(pb=stackOut.b);r_in_sum-=pr;g_in_sum-=pg;b_in_sum-=pb;stackOut=stackOut.next;yi+=width}}context.putImageData(imageData,top_x,top_y)}function BlurStack(){this.r=0;this.g=0;this.b=0;this.a=0;this.next=null} // https://gist.github.com/mjackson/5311256 function rgbToHsl(r, g, b){ r /= 255, g /= 255, b /= 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if(max == min){ h = s = 0; // achromatic }else{ var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch(max){ case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h, s, l]; } function hslToRgb(h, s, l){ var r, g, b; if(s == 0){ r = g = b = l; // achromatic }else{ var hue2rgb = function hue2rgb(p, q, t){ if(t < 0) t += 1; if(t > 1) t -= 1; if(t < 1/6) return p + (q - p) * 6 * t; if(t < 1/2) return q; if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; } function lightBlur(hsl) { // Adjust the luminance. let lumCalc = 0.35 + (0.3 / hsl[2]); if (lumCalc < 1) { lumCalc = 1; } else if (lumCalc > 3.3) { lumCalc = 3.3; } const l = hsl[2] * lumCalc; // Adjust the saturation. const colorful = 2 * hsl[1] * l; const s = hsl[1] * colorful * 1.5; return [hsl[0],s,l]; } function darkBlur(hsl) { // Adjust the saturation. const colorful = 2 * hsl[1] * hsl[2]; const s = hsl[1] * (1 - hsl[2]) * 3; return [hsl[0],s,hsl[2]]; } // Set up the canvas. const img = document.getElementById("blurImg"); const canvas = document.getElementById("mainCanvas"); const w = img.naturalWidth; const h = img.naturalHeight; canvas.style.width = w + "px"; canvas.style.height = h + "px"; canvas.width = w; canvas.height = h; const context = canvas.getContext("2d"); context.clearRect( 0, 0, w, h ); context.drawImage( img, 0, 0 ); // Get the image data from the context. var imageData = context.getImageData(0,0,w,h); var pix = imageData.data; var isDark = "${style}" == "dark"; var imageFunc = isDark ? darkBlur : lightBlur; for (let i=0; i < pix.length; i+=4) { // Convert to HSL. let hsl = rgbToHsl(pix[i],pix[i+1],pix[i+2]); // Apply the image function. hsl = imageFunc(hsl); // Convert back to RGB. const rgb = hslToRgb(hsl[0], hsl[1], hsl[2]); // Put the values back into the data. pix[i] = rgb[0]; pix[i+1] = rgb[1]; pix[i+2] = rgb[2]; } // Draw over the old image. context.putImageData(imageData,0,0); // Blur the image. stackBlurCanvasRGB("mainCanvas", 0, 0, w, h, ${blur}); // Perform the additional processing for dark images. if (isDark) { // Draw the hard light box over it. context.globalCompositeOperation = "hard-light"; context.fillStyle = "rgba(55,55,55,0.2)"; context.fillRect(0, 0, w, h); // Draw the soft light box over it. context.globalCompositeOperation = "soft-light"; context.fillStyle = "rgba(55,55,55,1)"; context.fillRect(0, 0, w, h); // Draw the regular box over it. context.globalCompositeOperation = "source-over"; context.fillStyle = "rgba(55,55,55,0.4)"; context.fillRect(0, 0, w, h); // Otherwise process light images. } else { context.fillStyle = "rgba(255,255,255,0.4)"; context.fillRect(0, 0, w, h); } // Return a base64 representation. canvas.toDataURL(); ` // Convert the images and create the HTML. let blurImgData = Data.fromPNG(img).toBase64String() let html = ` <img id="blurImg" src="data:image/png;base64,${blurImgData}" /> <canvas id="mainCanvas" /> ` // Make the web view and get its return value. let view = new WebView() await view.loadHTML(html) let returnValue = await view.evaluateJavaScript(js) // Remove the data type from the string and convert to data. let imageDataString = returnValue.slice(22) let imageData = Data.fromBase64String(imageDataString) // Convert to image and crop before returning. let imageFromData = Image.fromData(imageData) // return cropImage(imageFromData) return imageFromData }
创立弹窗
async function generateAlert(message, options) { let alert = new Alert(); alert.message = message; for (const option of options) { alert.addAction(option); } let response = await alert.presentAlert(); return response;}
弹出一个告诉
/** * 弹出一个告诉 * @param {string} title 告诉题目 * @param {string} body 告诉内容 * @param {string} url 点击后关上的URL*/async notify (title, body, url, opts = {}) { let n = new Notification() n = Object.assign(n, opts); n.title = title n.body = body if (url) n.openURL = url return await n.schedule()}
应用教程
- AppStore搜寻下载Scriptable
- 关上Scriptable,点击右上角➕,粘贴从小物件屋小程序里复制的装置小组件代码
- 点击右下角▶️运行按钮进行下载安装组件代码,若须要配置小物件(如: 设置背景图片等),会弹出弹窗,依据提醒下一步操作即可,若无任何反馈则示意无需配置,接下去点击左上角的Done按钮即可
回到iPhone桌面,长按,增加组件,抉择Scriptable利用,勾选刚刚增加的小组件代码,实现显示成果